rambling-trie-opal 2.1.1
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 +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
|