rambling-trie 1.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Gemfile +6 -3
- data/Guardfile +3 -1
- data/README.md +30 -12
- data/Rakefile +8 -0
- data/lib/rambling-trie.rb +2 -0
- data/lib/rambling/trie.rb +48 -26
- data/lib/rambling/trie/comparable.rb +6 -3
- data/lib/rambling/trie/compressible.rb +16 -0
- data/lib/rambling/trie/compressor.rb +39 -24
- data/lib/rambling/trie/configuration.rb +3 -1
- data/lib/rambling/trie/configuration/properties.rb +18 -9
- data/lib/rambling/trie/configuration/provider_collection.rb +38 -17
- data/lib/rambling/trie/container.rb +123 -36
- data/lib/rambling/trie/enumerable.rb +6 -4
- data/lib/rambling/trie/inspectable.rb +2 -0
- data/lib/rambling/trie/invalid_operation.rb +3 -1
- data/lib/rambling/trie/nodes.rb +13 -0
- data/lib/rambling/trie/nodes/compressed.rb +98 -0
- data/lib/rambling/trie/nodes/missing.rb +12 -0
- data/lib/rambling/trie/nodes/node.rb +183 -0
- data/lib/rambling/trie/nodes/raw.rb +82 -0
- data/lib/rambling/trie/readers.rb +3 -1
- data/lib/rambling/trie/readers/plain_text.rb +3 -11
- data/lib/rambling/trie/serializers.rb +3 -1
- data/lib/rambling/trie/serializers/file.rb +2 -0
- data/lib/rambling/trie/serializers/marshal.rb +15 -5
- data/lib/rambling/trie/serializers/yaml.rb +21 -5
- data/lib/rambling/trie/serializers/zip.rb +15 -8
- data/lib/rambling/trie/stringifyable.rb +8 -2
- data/lib/rambling/trie/version.rb +3 -1
- data/rambling-trie.gemspec +21 -10
- data/spec/assets/test_words.es_DO.txt +1 -0
- data/spec/integration/rambling/trie_spec.rb +44 -35
- data/spec/lib/rambling/trie/comparable_spec.rb +8 -15
- data/spec/lib/rambling/trie/compressor_spec.rb +90 -13
- data/spec/lib/rambling/trie/configuration/properties_spec.rb +21 -13
- data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +18 -34
- data/spec/lib/rambling/trie/container_spec.rb +183 -217
- data/spec/lib/rambling/trie/enumerable_spec.rb +14 -9
- data/spec/lib/rambling/trie/inspectable_spec.rb +36 -11
- data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
- data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
- data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
- data/spec/lib/rambling/trie/readers/plain_text_spec.rb +3 -1
- data/spec/lib/rambling/trie/serializers/file_spec.rb +6 -4
- data/spec/lib/rambling/trie/serializers/marshal_spec.rb +5 -7
- data/spec/lib/rambling/trie/serializers/yaml_spec.rb +5 -7
- data/spec/lib/rambling/trie/serializers/zip_spec.rb +18 -20
- data/spec/lib/rambling/trie/stringifyable_spec.rb +14 -11
- data/spec/lib/rambling/trie_spec.rb +18 -11
- data/spec/spec_helper.rb +10 -5
- data/spec/support/config.rb +10 -0
- data/spec/support/helpers/add_word.rb +20 -0
- data/spec/support/helpers/one_line_heredoc.rb +11 -0
- data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
- data/spec/support/shared_examples/a_serializable_trie.rb +10 -6
- data/spec/support/shared_examples/a_serializer.rb +9 -1
- data/spec/support/shared_examples/a_trie_data_structure.rb +2 -0
- data/spec/support/shared_examples/a_trie_node.rb +127 -0
- data/spec/{lib/rambling/trie/compressed_node_spec.rb → support/shared_examples/a_trie_node_implementation.rb} +25 -72
- metadata +42 -31
- data/lib/rambling/trie/compressable.rb +0 -14
- data/lib/rambling/trie/compressed_node.rb +0 -120
- data/lib/rambling/trie/missing_node.rb +0 -8
- data/lib/rambling/trie/node.rb +0 -97
- data/lib/rambling/trie/raw_node.rb +0 -96
- data/spec/lib/rambling/trie/node_spec.rb +0 -86
- data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
- data/spec/support/shared_examples/a_compressable_trie.rb +0 -26
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rambling
|
2
4
|
module Trie
|
3
5
|
# Provides the String representation behavior for the trie data structure.
|
@@ -6,14 +8,18 @@ module Rambling
|
|
6
8
|
# @return [String] the string representation of the current node.
|
7
9
|
# @raise [InvalidOperation] if node is not terminal or is root.
|
8
10
|
def as_word
|
9
|
-
|
11
|
+
if letter && !terminal?
|
12
|
+
raise Rambling::Trie::InvalidOperation,
|
13
|
+
'Cannot represent branch as a word'
|
14
|
+
end
|
15
|
+
|
10
16
|
to_s
|
11
17
|
end
|
12
18
|
|
13
19
|
# String representation of the current node.
|
14
20
|
# @return [String] the string representation of the current node.
|
15
21
|
def to_s
|
16
|
-
parent.to_s
|
22
|
+
parent.to_s + letter.to_s
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
data/rambling-trie.gemspec
CHANGED
@@ -1,26 +1,37 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
3
4
|
require 'rambling/trie/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |gem|
|
6
7
|
gem.authors = ['Edgar Gonzalez', 'Lilibeth De La Cruz']
|
7
8
|
gem.email = ['edggonzalezg@gmail.com', 'lilibethdlc@gmail.com']
|
8
|
-
|
9
|
+
|
10
|
+
gem.description = <<~DESCRIPTION.gsub(%r{\s+}, ' ')
|
11
|
+
The Rambling Trie is a Ruby implementation of the trie data structure, which
|
12
|
+
includes compression abilities and is designed to be very fast to traverse.
|
13
|
+
DESCRIPTION
|
14
|
+
|
9
15
|
gem.summary = 'A Ruby implementation of the trie data structure.'
|
10
16
|
gem.homepage = 'http://github.com/gonzedge/rambling-trie'
|
11
17
|
gem.date = Time.now.strftime '%Y-%m-%d'
|
12
18
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
executables = `git ls-files -- bin/*`.split "\n"
|
20
|
+
files = `git ls-files -- {lib,*file,*.gemspec,LICENSE*,README*}`.split "\n"
|
21
|
+
test_files = `git ls-files -- {test,spec,features}/*`.split "\n"
|
22
|
+
|
23
|
+
gem.executables = executables.map { |f| File.basename f }
|
24
|
+
gem.files = files
|
25
|
+
gem.test_files = test_files
|
26
|
+
gem.require_paths = %w(lib)
|
17
27
|
|
18
28
|
gem.name = 'rambling-trie'
|
19
29
|
gem.license = 'MIT'
|
20
30
|
gem.version = Rambling::Trie::VERSION
|
21
31
|
gem.platform = Gem::Platform::RUBY
|
32
|
+
gem.required_ruby_version = '>= 2.5', '<= 3.0.0'
|
22
33
|
|
23
|
-
gem.add_development_dependency '
|
24
|
-
gem.add_development_dependency '
|
25
|
-
gem.add_development_dependency 'yard', '~> 0.9.
|
34
|
+
gem.add_development_dependency 'rake', '~> 13.0'
|
35
|
+
gem.add_development_dependency 'rspec', '~> 3.9'
|
36
|
+
gem.add_development_dependency 'yard', '~> 0.9.25'
|
26
37
|
end
|
@@ -1,61 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
4
|
+
require 'zip'
|
2
5
|
|
3
6
|
describe Rambling::Trie do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
let(:assets_path) { File.join ::SPEC_ROOT, 'assets' }
|
8
|
+
|
9
|
+
context 'when providing words directly' do
|
10
|
+
it_behaves_like 'a compressible trie' do
|
11
|
+
let(:trie) { Rambling::Trie.create }
|
12
|
+
let(:words) { %w(a couple of words for our full trie integration test) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
trie.concat words
|
16
|
+
end
|
8
17
|
end
|
9
18
|
end
|
10
19
|
|
11
|
-
|
12
|
-
it_behaves_like 'a
|
13
|
-
let(:
|
14
|
-
let(:words)
|
15
|
-
|
20
|
+
context 'when provided with words with unicode characters' do
|
21
|
+
it_behaves_like 'a compressible trie' do
|
22
|
+
let(:trie) { Rambling::Trie.create }
|
23
|
+
let(:words) do
|
24
|
+
%w(poquísimas palabras para nuestra prueba de integración completa 🙃)
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
trie.concat words
|
29
|
+
end
|
16
30
|
end
|
17
31
|
end
|
18
32
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
33
|
+
context 'when provided with a filepath' do
|
34
|
+
let(:trie) { Rambling::Trie.create filepath }
|
35
|
+
let(:words) { File.readlines(filepath).map(&:chomp) }
|
36
|
+
|
37
|
+
context 'with english words' do
|
38
|
+
it_behaves_like 'a compressible trie' do
|
39
|
+
let(:filepath) { File.join assets_path, 'test_words.en_US.txt' }
|
40
|
+
end
|
23
41
|
end
|
24
|
-
end
|
25
42
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
let(:trie) { Rambling::Trie.create filepath }
|
43
|
+
context 'with unicode characters' do
|
44
|
+
it_behaves_like 'a compressible trie' do
|
45
|
+
let(:filepath) { File.join assets_path, 'test_words.es_DO.txt' }
|
46
|
+
end
|
31
47
|
end
|
32
48
|
end
|
33
49
|
|
34
|
-
describe '
|
35
|
-
let(:words_filepath) { File.join
|
36
|
-
let(:words) { File.readlines(words_filepath).map
|
37
|
-
let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
|
38
|
-
let(:trie_filename) { File.join ::SPEC_ROOT, '..', 'tmp', 'trie-root' }
|
50
|
+
describe 'dump and load' do
|
51
|
+
let(:words_filepath) { File.join assets_path, 'test_words.en_US.txt' }
|
52
|
+
let(:words) { File.readlines(words_filepath).map(&:chomp) }
|
39
53
|
|
40
54
|
context 'when serialized with Ruby marshal format (default)' do
|
41
55
|
it_behaves_like 'a serializable trie' do
|
42
|
-
let(:
|
43
|
-
let(:
|
44
|
-
let(:serializer) { nil }
|
56
|
+
let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
|
57
|
+
let(:format) { :marshal }
|
45
58
|
end
|
46
59
|
end
|
47
60
|
|
48
61
|
context 'when serialized with YAML' do
|
49
62
|
it_behaves_like 'a serializable trie' do
|
50
|
-
let(:
|
51
|
-
let(:
|
52
|
-
let(:serializer) { nil }
|
63
|
+
let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
|
64
|
+
let(:format) { :yml }
|
53
65
|
end
|
54
66
|
end
|
55
67
|
|
56
68
|
context 'when serialized with zipped Ruby marshal format' do
|
57
69
|
before do
|
58
|
-
require 'zip'
|
59
70
|
@original_on_exists_proc = ::Zip.on_exists_proc
|
60
71
|
@original_continue_on_exists_proc = ::Zip.continue_on_exists_proc
|
61
72
|
::Zip.on_exists_proc = true
|
@@ -63,15 +74,13 @@ describe Rambling::Trie do
|
|
63
74
|
end
|
64
75
|
|
65
76
|
after do
|
66
|
-
require 'zip'
|
67
77
|
::Zip.on_exists_proc = @original_on_exists_proc
|
68
78
|
::Zip.continue_on_exists_proc = @original_continue_on_exists_proc
|
69
79
|
end
|
70
80
|
|
71
81
|
it_behaves_like 'a serializable trie' do
|
72
|
-
let(:
|
73
|
-
let(:
|
74
|
-
let(:serializer) { nil }
|
82
|
+
let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
|
83
|
+
let(:format) { 'marshal.zip' }
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Rambling::Trie::Comparable do
|
4
6
|
describe '#==' do
|
5
|
-
let(:node_1) { Rambling::Trie::
|
6
|
-
let(:node_2) { Rambling::Trie::
|
7
|
+
let(:node_1) { Rambling::Trie::Nodes::Raw.new }
|
8
|
+
let(:node_2) { Rambling::Trie::Nodes::Raw.new }
|
7
9
|
|
8
10
|
context 'when the nodes do not have the same letter' do
|
9
11
|
before do
|
@@ -67,37 +69,28 @@ describe Rambling::Trie::Comparable do
|
|
67
69
|
context 'when the nodes have the same letter and the same children' do
|
68
70
|
before do
|
69
71
|
node_1.letter = :t
|
70
|
-
node_1
|
71
|
-
node_1.add 'hree'
|
72
|
-
node_1.add 'hings'
|
72
|
+
add_words node_1, %w(hese hree hings)
|
73
73
|
|
74
74
|
node_2.letter = :t
|
75
|
-
node_2
|
76
|
-
node_2.add 'hree'
|
77
|
-
node_2.add 'hings'
|
75
|
+
add_words node_2, %w(hese hree hings)
|
78
76
|
end
|
79
77
|
|
80
78
|
it 'returns true' do
|
81
79
|
expect(node_1).to eq node_2
|
82
|
-
expect(node_1[:h][:e][:s][:e]).to eq node_2[:h][:e][:s][:e]
|
83
80
|
end
|
84
81
|
end
|
85
82
|
|
86
83
|
context 'when the nodes have the same letter but different children' do
|
87
84
|
before do
|
88
85
|
node_1.letter = :t
|
89
|
-
node_1
|
90
|
-
node_1.add 'wo'
|
86
|
+
add_words node_1, %w(hese wo)
|
91
87
|
|
92
88
|
node_2.letter = :t
|
93
|
-
node_2
|
94
|
-
node_2.add 'hree'
|
95
|
-
node_2.add 'hings'
|
89
|
+
add_words node_2, %w(hese hree hings)
|
96
90
|
end
|
97
91
|
|
98
92
|
it 'returns false' do
|
99
93
|
expect(node_1).not_to eq node_2
|
100
|
-
expect(node_1[:h][:e][:s][:e]).to eq node_2[:h][:e][:s][:e]
|
101
94
|
end
|
102
95
|
end
|
103
96
|
end
|
@@ -1,31 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Rambling::Trie::Compressor do
|
4
6
|
let(:compressor) { Rambling::Trie::Compressor.new }
|
5
7
|
|
6
8
|
describe '#compress' do
|
7
|
-
let(:
|
8
|
-
|
9
|
-
|
9
|
+
let(:node) { Rambling::Trie::Nodes::Raw.new }
|
10
|
+
|
11
|
+
it 'compresses the node' do
|
12
|
+
add_words node, %w(a few words hello hell)
|
13
|
+
compressed = compressor.compress node
|
14
|
+
|
15
|
+
expect(compressed.children_tree.keys).to eq %i(a f w h)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with at least one word' do
|
19
|
+
before do
|
20
|
+
add_words node, %w(all the words)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'keeps the node letter nil' do
|
24
|
+
compressed = compressor.compress node
|
25
|
+
|
26
|
+
expect(compressed.letter).to be_nil
|
27
|
+
end
|
10
28
|
end
|
11
29
|
|
12
|
-
|
13
|
-
|
30
|
+
context 'with a single word' do
|
31
|
+
before do
|
32
|
+
add_word node, 'all'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'compresses into a single node without children' do
|
36
|
+
compressed = compressor.compress node
|
37
|
+
|
38
|
+
expect(compressed[:a].letter).to eq :all
|
39
|
+
expect(compressed[:a].children.size).to eq 0
|
40
|
+
expect(compressed[:a]).to be_terminal
|
41
|
+
expect(compressed[:a]).to be_compressed
|
42
|
+
end
|
14
43
|
end
|
15
44
|
|
16
|
-
|
17
|
-
|
45
|
+
context 'with two words' do
|
46
|
+
before do
|
47
|
+
add_words node, %w(all ask)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'compresses into corresponding three nodes' do
|
51
|
+
compressed = compressor.compress node
|
52
|
+
|
53
|
+
expect(compressed[:a].letter).to eq :a
|
54
|
+
expect(compressed[:a].children.size).to eq 2
|
55
|
+
|
56
|
+
expect(compressed[:a][:l].letter).to eq :ll
|
57
|
+
expect(compressed[:a][:s].letter).to eq :sk
|
58
|
+
|
59
|
+
expect(compressed[:a][:l].children.size).to eq 0
|
60
|
+
expect(compressed[:a][:s].children.size).to eq 0
|
18
61
|
|
19
|
-
|
20
|
-
|
21
|
-
|
62
|
+
expect(compressed[:a][:l]).to be_terminal
|
63
|
+
expect(compressed[:a][:s]).to be_terminal
|
64
|
+
|
65
|
+
expect(compressed[:a][:l]).to be_compressed
|
66
|
+
expect(compressed[:a][:s]).to be_compressed
|
22
67
|
end
|
23
68
|
end
|
24
69
|
|
25
|
-
it '
|
26
|
-
|
70
|
+
it 'reassigns the parent nodes correctly' do
|
71
|
+
add_words node, %w(repay rest repaint)
|
72
|
+
compressed = compressor.compress node
|
73
|
+
|
74
|
+
expect(compressed[:r].letter).to eq :re
|
75
|
+
expect(compressed[:r].parent).to eq compressed
|
76
|
+
expect(compressed[:r].children.size).to eq 2
|
77
|
+
|
78
|
+
expect(compressed[:r][:p].letter).to eq :pa
|
79
|
+
expect(compressed[:r][:p].parent).to eq compressed[:r]
|
80
|
+
expect(compressed[:r][:p].children.size).to eq 2
|
81
|
+
|
82
|
+
expect(compressed[:r][:s].letter).to eq :st
|
83
|
+
expect(compressed[:r][:s].parent).to eq compressed[:r]
|
84
|
+
expect(compressed[:r][:s].children.size).to eq 0
|
85
|
+
|
86
|
+
expect(compressed[:r][:p][:y].letter).to eq :y
|
87
|
+
expect(compressed[:r][:p][:y].parent).to eq compressed[:r][:p]
|
88
|
+
expect(compressed[:r][:p][:y].children.size).to eq 0
|
89
|
+
|
90
|
+
expect(compressed[:r][:p][:i].letter).to eq :int
|
91
|
+
expect(compressed[:r][:p][:i].parent).to eq compressed[:r][:p]
|
92
|
+
expect(compressed[:r][:p][:i].children.size).to eq 0
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'does not compress terminal nodes' do
|
96
|
+
add_words node, %w(you your yours)
|
97
|
+
compressed = compressor.compress node
|
98
|
+
|
99
|
+
expect(compressed[:y].letter).to eq :you
|
100
|
+
|
101
|
+
expect(compressed[:y][:r].letter).to eq :r
|
102
|
+
expect(compressed[:y][:r]).to be_compressed
|
27
103
|
|
28
|
-
expect(
|
104
|
+
expect(compressed[:y][:r][:s].letter).to eq :s
|
105
|
+
expect(compressed[:y][:r][:s]).to be_compressed
|
29
106
|
end
|
30
107
|
end
|
31
108
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Rambling::Trie::Configuration::Properties do
|
@@ -6,27 +8,33 @@ describe Rambling::Trie::Configuration::Properties do
|
|
6
8
|
describe '.new' do
|
7
9
|
it 'configures the serializers' do
|
8
10
|
serializers = properties.serializers
|
9
|
-
expect(serializers.keys).to match_array %i(marshal yaml yml zip)
|
10
11
|
|
11
|
-
expect(serializers
|
12
|
-
expect(serializers
|
13
|
-
|
14
|
-
|
12
|
+
expect(serializers.formats).to match_array %i(marshal yaml yml zip)
|
13
|
+
expect(serializers.providers.to_a).to match_array [
|
14
|
+
[:marshal, Rambling::Trie::Serializers::Marshal],
|
15
|
+
[:yaml, Rambling::Trie::Serializers::Yaml],
|
16
|
+
[:yml, Rambling::Trie::Serializers::Yaml],
|
17
|
+
[:zip, Rambling::Trie::Serializers::Zip],
|
18
|
+
]
|
15
19
|
end
|
16
20
|
|
17
21
|
it 'configures the readers' do
|
18
22
|
readers = properties.readers
|
19
|
-
expect(readers.keys).to match_array %i(txt)
|
20
23
|
|
21
|
-
expect(readers
|
24
|
+
expect(readers.formats).to match_array %i(txt)
|
25
|
+
expect(readers.providers.to_a).to match_array [
|
26
|
+
[:txt, Rambling::Trie::Readers::PlainText],
|
27
|
+
]
|
22
28
|
end
|
23
29
|
|
24
30
|
it 'configures the compressor' do
|
25
|
-
|
31
|
+
compressor = properties.compressor
|
32
|
+
expect(compressor).to be_instance_of Rambling::Trie::Compressor
|
26
33
|
end
|
27
34
|
|
28
35
|
it 'configures the root_builder' do
|
29
|
-
|
36
|
+
root = properties.root_builder.call
|
37
|
+
expect(root).to be_instance_of Rambling::Trie::Nodes::Raw
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
@@ -37,13 +45,13 @@ describe Rambling::Trie::Configuration::Properties do
|
|
37
45
|
end
|
38
46
|
|
39
47
|
it 'resets the serializers and readers to initial values' do
|
40
|
-
expect(properties.serializers.
|
41
|
-
expect(properties.readers.
|
48
|
+
expect(properties.serializers.formats).to include :test
|
49
|
+
expect(properties.readers.formats).to include :test
|
42
50
|
|
43
51
|
properties.reset
|
44
52
|
|
45
|
-
expect(properties.serializers.
|
46
|
-
expect(properties.readers.
|
53
|
+
expect(properties.serializers.formats).not_to include :test
|
54
|
+
expect(properties.readers.formats).not_to include :test
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|