rambling-trie-opal 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +26 -0
- data/Guardfile +10 -0
- data/LICENSE +26 -0
- data/README.md +301 -0
- data/Rakefile +15 -0
- data/lib/rambling-trie.rb +3 -0
- data/lib/rambling/trie.rb +119 -0
- data/lib/rambling/trie/comparable.rb +19 -0
- data/lib/rambling/trie/compressible.rb +16 -0
- data/lib/rambling/trie/compressor.rb +64 -0
- data/lib/rambling/trie/configuration.rb +16 -0
- data/lib/rambling/trie/configuration/properties.rb +75 -0
- data/lib/rambling/trie/configuration/provider_collection.rb +122 -0
- data/lib/rambling/trie/container.rb +226 -0
- data/lib/rambling/trie/enumerable.rb +29 -0
- data/lib/rambling/trie/inspectable.rb +39 -0
- data/lib/rambling/trie/invalid_operation.rb +15 -0
- data/lib/rambling/trie/nodes.rb +18 -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 +15 -0
- data/lib/rambling/trie/readers/plain_text.rb +18 -0
- data/lib/rambling/trie/serializers.rb +18 -0
- data/lib/rambling/trie/serializers/file.rb +27 -0
- data/lib/rambling/trie/serializers/marshal.rb +48 -0
- data/lib/rambling/trie/serializers/yaml.rb +55 -0
- data/lib/rambling/trie/serializers/zip.rb +74 -0
- data/lib/rambling/trie/stringifyable.rb +26 -0
- data/lib/rambling/trie/version.rb +8 -0
- data/rambling-trie-opal.gemspec +36 -0
- data/spec/assets/test_words.en_US.txt +23 -0
- data/spec/assets/test_words.es_DO.txt +24 -0
- data/spec/integration/rambling/trie_spec.rb +87 -0
- data/spec/lib/rambling/trie/comparable_spec.rb +97 -0
- data/spec/lib/rambling/trie/compressor_spec.rb +108 -0
- data/spec/lib/rambling/trie/configuration/properties_spec.rb +57 -0
- data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +149 -0
- data/spec/lib/rambling/trie/container_spec.rb +591 -0
- data/spec/lib/rambling/trie/enumerable_spec.rb +42 -0
- data/spec/lib/rambling/trie/inspectable_spec.rb +56 -0
- 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 +16 -0
- data/spec/lib/rambling/trie/serializers/file_spec.rb +13 -0
- data/spec/lib/rambling/trie/serializers/marshal_spec.rb +12 -0
- data/spec/lib/rambling/trie/serializers/yaml_spec.rb +12 -0
- data/spec/lib/rambling/trie/serializers/zip_spec.rb +28 -0
- data/spec/lib/rambling/trie/stringifyable_spec.rb +85 -0
- data/spec/lib/rambling/trie_spec.rb +182 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/config.rb +15 -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 +30 -0
- data/spec/support/shared_examples/a_serializer.rb +37 -0
- data/spec/support/shared_examples/a_trie_data_structure.rb +31 -0
- data/spec/support/shared_examples/a_trie_node.rb +127 -0
- data/spec/support/shared_examples/a_trie_node_implementation.rb +152 -0
- data/spec/tmp/.gitkeep +0 -0
- metadata +179 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Rambling::Trie::Comparable do
|
6
|
+
describe '#==' do
|
7
|
+
let(:node_1) { Rambling::Trie::Nodes::Raw.new }
|
8
|
+
let(:node_2) { Rambling::Trie::Nodes::Raw.new }
|
9
|
+
|
10
|
+
context 'when the nodes do not have the same letter' do
|
11
|
+
before do
|
12
|
+
node_1.letter = :a
|
13
|
+
node_2.letter = :b
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns false' do
|
17
|
+
expect(node_1).not_to eq node_2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when the nodes have the same letter and no children' do
|
22
|
+
before do
|
23
|
+
node_1.letter = :a
|
24
|
+
node_2.letter = :a
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns true' do
|
28
|
+
expect(node_1).to eq node_2
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the nodes have the same letter and are terminal' do
|
33
|
+
before do
|
34
|
+
node_1.letter = :a
|
35
|
+
node_1.terminal!
|
36
|
+
|
37
|
+
node_2.letter = :a
|
38
|
+
node_2.terminal!
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns true' do
|
42
|
+
expect(node_1).to eq node_2
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when the nodes have the same letter and are not terminal' do
|
47
|
+
before do
|
48
|
+
node_1.letter = :a
|
49
|
+
node_2.letter = :a
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns true' do
|
53
|
+
expect(node_1).to eq node_2
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when the nodes have the same letter but are not both terminal' do
|
58
|
+
before do
|
59
|
+
node_1.letter = :a
|
60
|
+
node_1.terminal!
|
61
|
+
|
62
|
+
node_2.letter = :a
|
63
|
+
end
|
64
|
+
it 'returns false' do
|
65
|
+
expect(node_1).not_to eq node_2
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when the nodes have the same letter and the same children' do
|
70
|
+
before do
|
71
|
+
node_1.letter = :t
|
72
|
+
add_words node_1, %w(hese hree hings)
|
73
|
+
|
74
|
+
node_2.letter = :t
|
75
|
+
add_words node_2, %w(hese hree hings)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'returns true' do
|
79
|
+
expect(node_1).to eq node_2
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when the nodes have the same letter but different children' do
|
84
|
+
before do
|
85
|
+
node_1.letter = :t
|
86
|
+
add_words node_1, %w(hese wo)
|
87
|
+
|
88
|
+
node_2.letter = :t
|
89
|
+
add_words node_2, %w(hese hree hings)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns false' do
|
93
|
+
expect(node_1).not_to eq node_2
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Rambling::Trie::Compressor do
|
6
|
+
let(:compressor) { Rambling::Trie::Compressor.new }
|
7
|
+
|
8
|
+
describe '#compress' do
|
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
|
28
|
+
end
|
29
|
+
|
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
|
43
|
+
end
|
44
|
+
|
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
|
61
|
+
|
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
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
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
|
103
|
+
|
104
|
+
expect(compressed[:y][:r][:s].letter).to eq :s
|
105
|
+
expect(compressed[:y][:r][:s]).to be_compressed
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Rambling::Trie::Configuration::Properties do
|
6
|
+
let(:properties) { Rambling::Trie::Configuration::Properties.new }
|
7
|
+
|
8
|
+
describe '.new' do
|
9
|
+
it 'configures the serializers' do
|
10
|
+
serializers = properties.serializers
|
11
|
+
|
12
|
+
expect(serializers.formats).to match_array %i(marshal yaml yml)
|
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
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'configures the readers' do
|
22
|
+
readers = properties.readers
|
23
|
+
|
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
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'configures the compressor' do
|
31
|
+
compressor = properties.compressor
|
32
|
+
expect(compressor).to be_instance_of Rambling::Trie::Compressor
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'configures the root_builder' do
|
36
|
+
root = properties.root_builder.call
|
37
|
+
expect(root).to be_instance_of Rambling::Trie::Nodes::Raw
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#reset' do
|
42
|
+
before do
|
43
|
+
properties.serializers.add :test, 'test'
|
44
|
+
properties.readers.add :test, 'test'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'resets the serializers and readers to initial values' do
|
48
|
+
expect(properties.serializers.formats).to include :test
|
49
|
+
expect(properties.readers.formats).to include :test
|
50
|
+
|
51
|
+
properties.reset
|
52
|
+
|
53
|
+
expect(properties.serializers.formats).not_to include :test
|
54
|
+
expect(properties.readers.formats).not_to include :test
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Rambling::Trie::Configuration::ProviderCollection do
|
6
|
+
let(:configured_default) { nil }
|
7
|
+
let(:configured_providers) do
|
8
|
+
{ one: first_provider, two: second_provider }
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:first_provider) { double :first_provider }
|
12
|
+
let(:second_provider) { double :second_provider }
|
13
|
+
|
14
|
+
let(:provider_collection) do
|
15
|
+
Rambling::Trie::Configuration::ProviderCollection.new(
|
16
|
+
:provider,
|
17
|
+
configured_providers,
|
18
|
+
configured_default,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.new' do
|
23
|
+
it 'has a name' do
|
24
|
+
expect(provider_collection.name).to eq :provider
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has the given providers' do
|
28
|
+
expect(provider_collection.providers)
|
29
|
+
.to eq one: first_provider, two: second_provider
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has a default provider' do
|
33
|
+
expect(provider_collection.default).to eq first_provider
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when a default is provided' do
|
37
|
+
let(:configured_default) { second_provider }
|
38
|
+
|
39
|
+
it 'has that as the default provider' do
|
40
|
+
expect(provider_collection.default).to eq second_provider
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'aliases and delegates' do
|
46
|
+
let(:providers) { provider_collection.providers }
|
47
|
+
|
48
|
+
before do
|
49
|
+
allow(providers) .to receive_messages(
|
50
|
+
:[] => 'value',
|
51
|
+
keys: %i(a b),
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'delegates `#[]` to providers' do
|
56
|
+
expect(provider_collection[:key]).to eq 'value'
|
57
|
+
expect(providers).to have_received(:[]).with :key
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'aliases `#formats` to `providers#keys`' do
|
61
|
+
expect(provider_collection.formats).to eq %i(a b)
|
62
|
+
expect(providers).to have_received :keys
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#add' do
|
67
|
+
let(:provider) { double :provider }
|
68
|
+
|
69
|
+
before do
|
70
|
+
provider_collection.add :three, provider
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'adds a new provider' do
|
74
|
+
expect(provider_collection.providers[:three]).to eq provider
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#default=' do
|
79
|
+
let(:other_provider) { double :other_provider }
|
80
|
+
|
81
|
+
context 'when the given value is in the providers list' do
|
82
|
+
it 'changes the default provider' do
|
83
|
+
provider_collection.default = second_provider
|
84
|
+
expect(provider_collection.default).to eq second_provider
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when the given value is not in the providers list' do
|
89
|
+
it 'raises an error and keeps the default provider' do
|
90
|
+
expect { provider_collection.default = other_provider }
|
91
|
+
.to raise_error(ArgumentError)
|
92
|
+
.and(not_change { provider_collection.default })
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'raises an ArgumentError' do
|
96
|
+
expect { provider_collection.default = other_provider }
|
97
|
+
.to raise_error ArgumentError
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when the providers list is empty' do
|
102
|
+
let(:configured_providers) { {} }
|
103
|
+
|
104
|
+
it 'accepts nil' do
|
105
|
+
provider_collection.default = nil
|
106
|
+
expect(provider_collection.default).to be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'raises an ArgumentError for any other provider' do
|
110
|
+
expect do
|
111
|
+
provider_collection.default = other_provider
|
112
|
+
end.to raise_error ArgumentError
|
113
|
+
expect(provider_collection.default).to be_nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#resolve' do
|
119
|
+
context 'when the file extension is one of the providers' do
|
120
|
+
it 'returns the corresponding provider' do
|
121
|
+
expect(provider_collection.resolve 'hola.one').to eq first_provider
|
122
|
+
expect(provider_collection.resolve 'hola.two').to eq second_provider
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when the file extension is not one of the providers' do
|
127
|
+
it 'returns the default provider' do
|
128
|
+
expect(provider_collection.resolve 'hola.unknown').to eq first_provider
|
129
|
+
expect(provider_collection.resolve 'hola').to eq first_provider
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#reset' do
|
135
|
+
let(:configured_default) { second_provider }
|
136
|
+
let(:provider) { double :provider }
|
137
|
+
|
138
|
+
before do
|
139
|
+
provider_collection.add :three, provider
|
140
|
+
provider_collection.default = provider
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'resets to back to the initially configured values' do
|
144
|
+
provider_collection.reset
|
145
|
+
expect(provider_collection[:three]).to be_nil
|
146
|
+
expect(provider_collection.default).to eq second_provider
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,591 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Rambling::Trie::Container do
|
6
|
+
let(:container) { Rambling::Trie::Container.new root, compressor }
|
7
|
+
let(:compressor) { Rambling::Trie::Compressor.new }
|
8
|
+
let(:root) { Rambling::Trie::Nodes::Raw.new }
|
9
|
+
|
10
|
+
describe '.new' do
|
11
|
+
it 'uses the provided node as root' do
|
12
|
+
expect(container.root).to be root
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'with a block' do
|
16
|
+
it 'yields the container' do
|
17
|
+
yielded = nil
|
18
|
+
|
19
|
+
container = Rambling::Trie::Container.new root, compressor do |c|
|
20
|
+
yielded = c
|
21
|
+
end
|
22
|
+
|
23
|
+
expect(yielded).to be container
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#add' do
|
29
|
+
it 'adds the word to the root node' do
|
30
|
+
add_word container, 'hello'
|
31
|
+
|
32
|
+
expect(root.children.size).to eq 1
|
33
|
+
expect(root.to_a).to eq %w(hello)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#concat' do
|
38
|
+
it 'adds all the words to the root node' do
|
39
|
+
container.concat %w(other words)
|
40
|
+
|
41
|
+
expect(root.children.size).to eq 2
|
42
|
+
expect(root.to_a).to eq %w(other words)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns all the corresponding nodes' do
|
46
|
+
nodes = container.concat %w(other words)
|
47
|
+
|
48
|
+
expect(nodes.first.letter).to eq :o
|
49
|
+
expect(nodes.last.letter).to eq :w
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#compress!' do
|
54
|
+
let(:node) { Rambling::Trie::Nodes::Compressed.new }
|
55
|
+
|
56
|
+
before do
|
57
|
+
allow(compressor).to receive(:compress).and_return node
|
58
|
+
|
59
|
+
add_word root, 'yes'
|
60
|
+
node[:yes] = Rambling::Trie::Nodes::Compressed.new
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'compresses the trie using the compressor' do
|
64
|
+
container.compress!
|
65
|
+
|
66
|
+
expect(compressor).to have_received(:compress).with root
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'changes to the root returned by the compressor' do
|
70
|
+
container.compress!
|
71
|
+
|
72
|
+
expect(container.root).not_to eq root
|
73
|
+
expect(container.root).to eq node
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns itself' do
|
77
|
+
expect(container.compress!).to eq container
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does not compress multiple times' do
|
81
|
+
container.compress!
|
82
|
+
allow(node).to receive(:compressed?).and_return(true)
|
83
|
+
|
84
|
+
container.compress!
|
85
|
+
expect(compressor).to have_received(:compress).once
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#compress' do
|
90
|
+
let(:node) { Rambling::Trie::Nodes::Compressed.new }
|
91
|
+
|
92
|
+
before do
|
93
|
+
allow(compressor).to receive(:compress).and_return node
|
94
|
+
|
95
|
+
add_word root, 'yes'
|
96
|
+
node[:yes] = Rambling::Trie::Nodes::Compressed.new
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'compresses the trie using the compressor' do
|
100
|
+
container.compress
|
101
|
+
|
102
|
+
expect(compressor).to have_received(:compress).with root
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns a container with the new root' do
|
106
|
+
new_container = container.compress
|
107
|
+
|
108
|
+
expect(new_container.root).not_to eq root
|
109
|
+
expect(new_container.root).to eq node
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns a new container' do
|
113
|
+
expect(container.compress).not_to eq container
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'can compress multiple times' do
|
117
|
+
container.compress
|
118
|
+
container.compress
|
119
|
+
|
120
|
+
expect(compressor).to have_received(:compress).twice
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'cannot compress the result' do
|
124
|
+
new_container = container.compress
|
125
|
+
new_container.compress
|
126
|
+
|
127
|
+
expect(compressor).to have_received(:compress).once
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#word?' do
|
132
|
+
let(:root) do
|
133
|
+
double :root,
|
134
|
+
compressed?: compressed,
|
135
|
+
word?: nil
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'for an uncompressed root' do
|
139
|
+
let(:compressed) { true }
|
140
|
+
|
141
|
+
it 'calls the root with the word characters' do
|
142
|
+
container.word? 'words'
|
143
|
+
expect(root).to have_received(:word?).with %w(w o r d s)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'for a compressed root' do
|
148
|
+
let(:compressed) { false }
|
149
|
+
|
150
|
+
it 'calls the root with the full word' do
|
151
|
+
container.word? 'words'
|
152
|
+
expect(root).to have_received(:word?).with %w(w o r d s)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#partial_word?' do
|
158
|
+
let(:root) do
|
159
|
+
double :root,
|
160
|
+
compressed?: compressed,
|
161
|
+
partial_word?: nil
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'for an uncompressed root' do
|
165
|
+
let(:compressed) { true }
|
166
|
+
|
167
|
+
it 'calls the root with the word characters' do
|
168
|
+
container.partial_word? 'words'
|
169
|
+
expect(root).to have_received(:partial_word?).with %w(w o r d s)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'for a compressed root' do
|
174
|
+
let(:compressed) { false }
|
175
|
+
|
176
|
+
it 'calls the root with the word characters' do
|
177
|
+
container.partial_word? 'words'
|
178
|
+
expect(root).to have_received(:partial_word?).with %w(w o r d s)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'delegates and aliases' do
|
184
|
+
before do
|
185
|
+
allow(root).to receive_messages(
|
186
|
+
:[] => nil,
|
187
|
+
add: nil,
|
188
|
+
as_word: nil,
|
189
|
+
children: nil,
|
190
|
+
children_tree: nil,
|
191
|
+
compressed?: nil,
|
192
|
+
each: nil,
|
193
|
+
key?: nil,
|
194
|
+
inspect: nil,
|
195
|
+
letter: nil,
|
196
|
+
parent: nil,
|
197
|
+
partial_word?: nil,
|
198
|
+
scan: nil,
|
199
|
+
size: nil,
|
200
|
+
to_s: nil,
|
201
|
+
word?: nil,
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'aliases `#include?` to `#word?`' do
|
206
|
+
container.include? 'words'
|
207
|
+
expect(root).to have_received(:word?).with %w(w o r d s)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'aliases `#match?` to `#partial_word?`' do
|
211
|
+
container.match? 'words'
|
212
|
+
expect(root).to have_received(:partial_word?).with %w(w o r d s)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'aliases `#words` to `#scan`' do
|
216
|
+
container.words 'hig'
|
217
|
+
expect(root).to have_received(:scan).with %w(h i g)
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'aliases `#<<` to `#add`' do
|
221
|
+
container << 'words'
|
222
|
+
expect(root).to have_received(:add).with %i(s d r o w)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'delegates `#[]` to the root node' do
|
226
|
+
container[:yep]
|
227
|
+
expect(root).to have_received(:[]).with :yep
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'delegates `#children` to the root node' do
|
231
|
+
container.children
|
232
|
+
expect(root).to have_received :children
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'delegates `#children_tree` to the root node' do
|
236
|
+
container.children_tree
|
237
|
+
expect(root).to have_received :children_tree
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'delegates `#compressed?` to the root node' do
|
241
|
+
container.compressed?
|
242
|
+
expect(root).to have_received :compressed?
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'delegates `#key?` to the root node' do
|
246
|
+
container.key? :yup
|
247
|
+
expect(root).to have_received(:key?).with :yup
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'aliases `#has_key?` to `#key?`' do
|
251
|
+
container.has_key? :yup
|
252
|
+
expect(root).to have_received(:key?).with :yup
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'aliases `#has_letter?` to `#has_key?`' do
|
256
|
+
container.has_letter? :yup
|
257
|
+
expect(root).to have_received(:key?).with :yup
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'delegates `#inspect` to the root node' do
|
261
|
+
container.inspect
|
262
|
+
expect(root).to have_received :inspect
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'delegates `#size` to the root node' do
|
266
|
+
container.size
|
267
|
+
expect(root).to have_received :size
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
describe '#compress!' do
|
272
|
+
it 'gets a new root from the compressor' do
|
273
|
+
container.compress!
|
274
|
+
|
275
|
+
expect(container.root).not_to be root
|
276
|
+
expect(container.root).to be_compressed
|
277
|
+
expect(root).not_to be_compressed
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'generates a new root with the words from the passed root' do
|
281
|
+
words = %w(a few words hello hell)
|
282
|
+
|
283
|
+
add_words container, words
|
284
|
+
container.compress!
|
285
|
+
|
286
|
+
words.each do |word|
|
287
|
+
expect(container).to include word
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe 'and trying to add a word' do
|
292
|
+
it 'raises an error' do
|
293
|
+
add_words container, %w(repay rest repaint)
|
294
|
+
container.compress!
|
295
|
+
|
296
|
+
expect do
|
297
|
+
add_word container, 'restaurant'
|
298
|
+
end.to raise_error Rambling::Trie::InvalidOperation
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
describe '#word?' do
|
304
|
+
context 'word is contained' do
|
305
|
+
before do
|
306
|
+
add_words container, %w(hello high)
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'matches the whole word' do
|
310
|
+
expect(container.word? 'hello').to be true
|
311
|
+
expect(container.word? 'high').to be true
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'and the root has been compressed' do
|
315
|
+
before do
|
316
|
+
container.compress!
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'matches the whole word' do
|
320
|
+
expect(container.word? 'hello').to be true
|
321
|
+
expect(container.word? 'high').to be true
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
context 'word is not contained' do
|
327
|
+
before do
|
328
|
+
add_word container, 'hello'
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'does not match the whole word' do
|
332
|
+
expect(container.word? 'halt').to be false
|
333
|
+
expect(container.word? 'al').to be false
|
334
|
+
end
|
335
|
+
|
336
|
+
context 'and the root has been compressed' do
|
337
|
+
before do
|
338
|
+
container.compress!
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'does not match the whole word' do
|
342
|
+
expect(container.word? 'halt').to be false
|
343
|
+
expect(container.word? 'al').to be false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe '#partial_word?' do
|
350
|
+
context 'word is contained' do
|
351
|
+
before do
|
352
|
+
add_words container, %w(hello high)
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'matches part of the word' do
|
356
|
+
expect(container.partial_word? 'hell').to be true
|
357
|
+
expect(container.partial_word? 'hig').to be true
|
358
|
+
end
|
359
|
+
|
360
|
+
context 'and the root has been compressed' do
|
361
|
+
before do
|
362
|
+
container.compress!
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'matches part of the word' do
|
366
|
+
expect(container.partial_word? 'h').to be true
|
367
|
+
expect(container.partial_word? 'he').to be true
|
368
|
+
expect(container.partial_word? 'hell').to be true
|
369
|
+
expect(container.partial_word? 'hello').to be true
|
370
|
+
expect(container.partial_word? 'hi').to be true
|
371
|
+
expect(container.partial_word? 'hig').to be true
|
372
|
+
expect(container.partial_word? 'high').to be true
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
shared_examples_for 'a non matching tree' do
|
378
|
+
it 'does not match any part of the word' do
|
379
|
+
%w(ha hal al).each do |word|
|
380
|
+
expect(container.partial_word? word).to be false
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
context 'word is not contained' do
|
386
|
+
before do
|
387
|
+
add_word container, 'hello'
|
388
|
+
end
|
389
|
+
|
390
|
+
context 'and the root is uncompressed' do
|
391
|
+
it_behaves_like 'a non matching tree'
|
392
|
+
end
|
393
|
+
|
394
|
+
context 'and the root has been compressed' do
|
395
|
+
it_behaves_like 'a non matching tree'
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
describe '#scan' do
|
401
|
+
context 'words that match are not contained' do
|
402
|
+
before do
|
403
|
+
add_words container, %w(hi hello high hell highlight histerical)
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'returns an array with the words that match' do
|
407
|
+
expect(container.scan 'hi').to eq %w(hi high highlight histerical)
|
408
|
+
expect(container.scan 'hig').to eq %w(high highlight)
|
409
|
+
end
|
410
|
+
|
411
|
+
context 'and the root has been compressed' do
|
412
|
+
before do
|
413
|
+
container.compress!
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'returns an array with the words that match' do
|
417
|
+
expect(container.scan 'hi').to eq %w(hi high highlight histerical)
|
418
|
+
expect(container.scan 'hig').to eq %w(high highlight)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
context 'words that match are not contained' do
|
424
|
+
before do
|
425
|
+
add_word container, 'hello'
|
426
|
+
end
|
427
|
+
|
428
|
+
it 'returns an empty array' do
|
429
|
+
expect(container.scan 'hi').to eq %w()
|
430
|
+
end
|
431
|
+
|
432
|
+
context 'and the root has been compressed' do
|
433
|
+
before do
|
434
|
+
container.compress!
|
435
|
+
end
|
436
|
+
|
437
|
+
it 'returns an empty array' do
|
438
|
+
expect(container.scan 'hi').to eq %w()
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
describe '#words_within' do
|
445
|
+
before do
|
446
|
+
add_words container, %w(one word and other words)
|
447
|
+
end
|
448
|
+
|
449
|
+
context 'phrase does not contain any words' do
|
450
|
+
it 'returns an empty array' do
|
451
|
+
expect(container.words_within 'xyz').to match_array []
|
452
|
+
end
|
453
|
+
|
454
|
+
context 'and the node is compressed' do
|
455
|
+
before do
|
456
|
+
container.compress!
|
457
|
+
end
|
458
|
+
|
459
|
+
it 'returns an empty array' do
|
460
|
+
expect(container.words_within 'xyz').to match_array []
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
context 'phrase contains one word at the start of the phrase' do
|
466
|
+
it 'returns an array with the word found in the phrase' do
|
467
|
+
expect(container.words_within 'word').to match_array %w(word)
|
468
|
+
expect(container.words_within 'wordxyz').to match_array %w(word)
|
469
|
+
end
|
470
|
+
|
471
|
+
context 'and the node is compressed' do
|
472
|
+
before do
|
473
|
+
container.compress!
|
474
|
+
end
|
475
|
+
|
476
|
+
it 'returns an array with the word found in the phrase' do
|
477
|
+
expect(container.words_within 'word').to match_array %w(word)
|
478
|
+
expect(container.words_within 'wordxyz').to match_array %w(word)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context 'phrase contains one word at the end of the phrase' do
|
484
|
+
it 'returns an array with the word found in the phrase' do
|
485
|
+
expect(container.words_within 'xyz word').to match_array %w(word)
|
486
|
+
end
|
487
|
+
|
488
|
+
context 'and the node is compressed' do
|
489
|
+
before do
|
490
|
+
container.compress!
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'returns an array with the word found in the phrase' do
|
494
|
+
expect(container.words_within 'xyz word').to match_array %w(word)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context 'phrase contains a few words' do
|
500
|
+
it 'returns an array with all words found in the phrase' do
|
501
|
+
expect(container.words_within 'xyzword otherzxyone')
|
502
|
+
.to match_array %w(word other one)
|
503
|
+
end
|
504
|
+
|
505
|
+
context 'and the node is compressed' do
|
506
|
+
before do
|
507
|
+
container.compress!
|
508
|
+
end
|
509
|
+
|
510
|
+
it 'returns an array with all words found in the phrase' do
|
511
|
+
expect(container.words_within 'xyzword otherzxyone')
|
512
|
+
.to match_array %w(word other one)
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
describe '#words_within?' do
|
519
|
+
before do
|
520
|
+
add_words container, %w(one word and other words)
|
521
|
+
end
|
522
|
+
|
523
|
+
context 'phrase does not contain any words' do
|
524
|
+
it 'returns false' do
|
525
|
+
expect(container.words_within? 'xyz').to be false
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
context 'phrase contains any word' do
|
530
|
+
it 'returns true' do
|
531
|
+
expect(container.words_within? 'xyz words').to be true
|
532
|
+
expect(container.words_within? 'xyzone word').to be true
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
describe '#==' do
|
538
|
+
context 'when the root nodes are the same' do
|
539
|
+
let(:other_container) do
|
540
|
+
Rambling::Trie::Container.new container.root, compressor
|
541
|
+
end
|
542
|
+
|
543
|
+
it 'returns true' do
|
544
|
+
expect(container).to eq other_container
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
context 'when the root nodes are not the same' do
|
549
|
+
let(:other_root) { Rambling::Trie::Nodes::Raw.new }
|
550
|
+
let(:other_container) do
|
551
|
+
Rambling::Trie::Container.new other_root, compressor
|
552
|
+
end
|
553
|
+
|
554
|
+
before do
|
555
|
+
add_word other_container, 'hola'
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'returns false' do
|
559
|
+
expect(container).not_to eq other_container
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
describe '#each' do
|
565
|
+
before do
|
566
|
+
add_words container, %w(yes no why)
|
567
|
+
end
|
568
|
+
|
569
|
+
it 'returns an enumerator when no block is given' do
|
570
|
+
expect(container.each).to be_instance_of Enumerator
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'iterates through all words contained' do
|
574
|
+
expect(container.each.to_a).to eq %w(yes no why)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
describe '#inspect' do
|
579
|
+
before do
|
580
|
+
add_words container, %w(a few words hello hell)
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'returns the container class name plus the root inspection' do
|
584
|
+
expect(container.inspect).to eq one_line <<~CONTAINER
|
585
|
+
#<Rambling::Trie::Container root: #<Rambling::Trie::Nodes::Raw letter: nil,
|
586
|
+
terminal: nil,
|
587
|
+
children: [:a, :f, :w, :h]>>
|
588
|
+
CONTAINER
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|