rambling-trie 0.8.1 → 0.9.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/lib/rambling/trie.rb +21 -9
  4. data/lib/rambling/trie/compressed_node.rb +112 -0
  5. data/lib/rambling/trie/compression.rb +13 -0
  6. data/lib/rambling/trie/compressor.rb +30 -31
  7. data/lib/rambling/trie/{root.rb → container.rb} +41 -38
  8. data/lib/rambling/trie/enumerable.rb +11 -7
  9. data/lib/rambling/trie/missing_node.rb +1 -1
  10. data/lib/rambling/trie/node.rb +25 -22
  11. data/lib/rambling/trie/plain_text_reader.rb +1 -1
  12. data/lib/rambling/trie/raw_node.rb +90 -0
  13. data/lib/rambling/trie/tasks/helpers/path.rb +13 -0
  14. data/lib/rambling/trie/tasks/helpers/time.rb +7 -0
  15. data/lib/rambling/trie/tasks/performance.rb +10 -91
  16. data/lib/rambling/trie/tasks/performance/all.rb +4 -0
  17. data/lib/rambling/trie/tasks/performance/benchmark.rb +172 -0
  18. data/lib/rambling/trie/tasks/performance/directory.rb +11 -0
  19. data/lib/rambling/trie/tasks/performance/profile/call_tree.rb +132 -0
  20. data/lib/rambling/trie/tasks/performance/profile/memory.rb +116 -0
  21. data/lib/rambling/trie/version.rb +1 -1
  22. data/rambling-trie.gemspec +6 -4
  23. data/spec/integration/rambling/trie_spec.rb +63 -9
  24. data/spec/lib/rambling/trie/compressed_node_spec.rb +35 -0
  25. data/spec/lib/rambling/trie/compressor_spec.rb +31 -0
  26. data/spec/lib/rambling/trie/container_spec.rb +470 -0
  27. data/spec/lib/rambling/trie/enumerable_spec.rb +2 -2
  28. data/spec/lib/rambling/trie/inspector_spec.rb +21 -14
  29. data/spec/lib/rambling/trie/node_spec.rb +72 -209
  30. data/spec/lib/rambling/trie/raw_node_spec.rb +377 -0
  31. data/spec/lib/rambling/trie_spec.rb +46 -25
  32. metadata +57 -16
  33. data/lib/rambling/trie/branches.rb +0 -149
  34. data/spec/lib/rambling/trie/branches_spec.rb +0 -52
  35. data/spec/lib/rambling/trie/root_spec.rb +0 -376
@@ -0,0 +1,116 @@
1
+ require_relative '../../helpers/path'
2
+ require_relative '../../helpers/time'
3
+
4
+ namespace :performance do
5
+ namespace :profile do
6
+ include Helpers::Path
7
+ include Helpers::Time
8
+
9
+ def with_gc_stats
10
+ puts "Live objects before - #{GC.stat[:heap_live_slots]}"
11
+ yield
12
+ puts "Live objects after - #{GC.stat[:heap_live_slots]}"
13
+ end
14
+
15
+ def memory_profile name
16
+ puts
17
+ puts name
18
+
19
+ result = MemoryProfiler.report allow_files: 'lib/rambling/trie', ignore_files: 'tasks/performance' do
20
+ yield
21
+ end
22
+
23
+ dir = path 'reports', Rambling::Trie::VERSION, 'memory', time
24
+ FileUtils.mkdir_p dir
25
+ result.pretty_print to_file: File.join(dir, name)
26
+ end
27
+
28
+ def dictionary
29
+ dictionary = path 'assets', 'dictionaries', 'words_with_friends.txt'
30
+ end
31
+
32
+ namespace :memory do
33
+ task creation: ['performance:directory'] do
34
+ puts 'Generating memory profiling reports for creation...'
35
+
36
+ trie = nil
37
+
38
+ memory_profile "memory-profile-new-trie" do
39
+ with_gc_stats { trie = Rambling::Trie.create dictionary }
40
+ end
41
+ end
42
+
43
+ task compression: ['performance:directory'] do
44
+ trie = Rambling::Trie.create dictionary
45
+
46
+ memory_profile "memory-profile-trie-and-compress" do
47
+ with_gc_stats { trie.compress! }
48
+ end
49
+
50
+ with_gc_stats { GC.start }
51
+ end
52
+
53
+ task lookups: ['performance:directory'] do
54
+ trie = Rambling::Trie.create dictionary
55
+ words = %w(hi help beautiful impressionism anthropological)
56
+
57
+ tries = [ trie, trie.clone.compress! ]
58
+
59
+ tries.each do |trie|
60
+ times = 10
61
+
62
+ name = "memory-profile-searching-#{trie.compressed? ? 'compressed' : 'uncompressed'}-trie-word"
63
+ memory_profile name do
64
+ with_gc_stats do
65
+ words.each do |word|
66
+ times.times do
67
+ trie.word? word
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ name = "memory-profile-searching-#{trie.compressed? ? 'compressed' : 'uncompressed'}-trie-partial-word"
74
+ memory_profile name do
75
+ with_gc_stats do
76
+ words.each do |word|
77
+ times.times do
78
+ trie.partial_word? word
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ task scans: ['performance:directory'] do
87
+ words = {
88
+ hi: 1,
89
+ help: 100,
90
+ beautiful: 100,
91
+ impressionism: 200,
92
+ anthropological: 200,
93
+ }
94
+
95
+ trie = Rambling::Trie.create dictionary
96
+ [ trie, trie.clone.compress! ].each do |trie|
97
+ name = "memory-profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-trie-scan"
98
+ memory_profile name do
99
+ words.each do |word, times|
100
+ times.times do
101
+ trie.scan(word.to_s).size
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ task all: [
109
+ 'performance:profile:memory:creation',
110
+ 'performance:profile:memory:compression',
111
+ 'performance:profile:memory:lookups',
112
+ 'performance:profile:memory:scans'
113
+ ]
114
+ end
115
+ end
116
+ end
@@ -1,6 +1,6 @@
1
1
  module Rambling
2
2
  module Trie
3
3
  # Current version of the rambling-trie.
4
- VERSION = '0.8.1'
4
+ VERSION = '0.9.0'
5
5
  end
6
6
  end
@@ -20,9 +20,11 @@ Gem::Specification.new do |gem|
20
20
  gem.version = Rambling::Trie::VERSION
21
21
  gem.platform = Gem::Platform::RUBY
22
22
 
23
- gem.add_development_dependency 'rspec', '~> 3.4'
24
- gem.add_development_dependency 'rake', '~> 10.5'
25
- gem.add_development_dependency 'ruby-prof', '~> 0.15.2'
26
- gem.add_development_dependency 'yard', '~> 0.8.7'
23
+ gem.add_development_dependency 'rspec', '~> 3.5'
24
+ gem.add_development_dependency 'rake', '~> 12.0'
25
+ gem.add_development_dependency 'ruby-prof', '~> 0.16.2'
26
+ gem.add_development_dependency 'memory_profiler', '~> 0.9.7'
27
+ gem.add_development_dependency 'benchmark-ips', '~> 2.7.2'
28
+ gem.add_development_dependency 'yard', '~> 0.9.5'
27
29
  gem.add_development_dependency 'redcarpet', '~> 3.3.4'
28
30
  end
@@ -1,20 +1,74 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Rambling::Trie do
4
- describe 'when a filepath is provided' do
5
- let(:filepath) { File.join ::SPEC_ROOT, 'assets', 'test_words.txt' }
6
- let(:words) { File.readlines(filepath).map &:chomp }
7
- subject { Rambling::Trie.create filepath }
4
+ shared_examples_for 'a compressable trie' do
5
+ context 'and the trie is not compressed' do
6
+ it_behaves_like 'a trie data structure'
8
7
 
8
+ it 'does not alter the input' do
9
+ word = 'string'
10
+ trie.add word
11
+
12
+ expect(word).to eq 'string'
13
+ end
14
+
15
+ it 'is marked as not compressed' do
16
+ expect(trie).not_to be_compressed
17
+ end
18
+ end
19
+
20
+ context 'and the trie is compressed' do
21
+ before { trie.compress! }
22
+
23
+ it_behaves_like 'a trie data structure'
24
+
25
+ it 'is marked as compressed' do
26
+ expect(trie).to be_compressed
27
+ end
28
+ end
29
+ end
30
+
31
+ shared_examples_for 'a trie data structure' do
9
32
  it 'contains all the words from the file' do
10
- words.each { |word| expect(subject).to include word }
33
+ words.each do |word|
34
+ expect(trie).to include word
35
+ expect(trie.word? word).to be true
36
+ end
11
37
  end
12
38
 
13
- describe 'and the trie is compressed' do
14
- it 'still contains all the words from the file' do
15
- subject.compress!
16
- words.each { |word| expect(subject).to include word }
39
+ it 'matches the start of all the words from the file' do
40
+ words.each do |word|
41
+ expect(trie.match? word).to be true
42
+ expect(trie.match? word[0..-2]).to be true
43
+ expect(trie.partial_word? word).to be true
44
+ expect(trie.partial_word? word[0..-2]).to be true
17
45
  end
18
46
  end
47
+
48
+ it 'allows iterating over all the words' do
49
+ expect(trie.to_a.sort).to eq words.sort
50
+ end
51
+ end
52
+
53
+ describe 'with words provided directly' do
54
+ it_behaves_like 'a compressable trie' do
55
+ let(:words) { %w[a couple of words for our full trie integration test] }
56
+ let(:trie) { Rambling::Trie.create }
57
+
58
+ before do
59
+ words.each do |word|
60
+ trie << word
61
+ trie.add word
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'with words from a file' do
68
+ it_behaves_like 'a compressable trie' do
69
+ let(:filepath) { File.join ::SPEC_ROOT, 'assets', 'test_words.txt' }
70
+ let(:words) { File.readlines(filepath).map &:chomp }
71
+ let(:trie) { Rambling::Trie.create filepath }
72
+ end
19
73
  end
20
74
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::CompressedNode do
4
+ let(:node) { Rambling::Trie::CompressedNode.new }
5
+
6
+ describe '#compressed?' do
7
+ it 'returns true' do
8
+ expect(node).to be_compressed
9
+ end
10
+ end
11
+
12
+ describe '.new' do
13
+ context 'with no parent' do
14
+ let(:node) { Rambling::Trie::CompressedNode.new }
15
+
16
+ it 'is marked as root' do
17
+ expect(node).to be_root
18
+ end
19
+ end
20
+
21
+ context 'with a specified' do
22
+ let(:node) { Rambling::Trie::CompressedNode.new double(:root) }
23
+
24
+ it 'is not marked as root' do
25
+ expect(node).not_to be_root
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '#add' do
31
+ it 'raises an error' do
32
+ expect { node.add 'restaurant' }.to raise_error Rambling::Trie::InvalidOperation
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Compressor do
4
+ let(:compressor) { Rambling::Trie::Compressor.new }
5
+
6
+ describe '#compress' do
7
+ let(:words) { %w(a few words hello hell) }
8
+ let(:root) do
9
+ Rambling::Trie::RawNode.new
10
+ end
11
+
12
+ before do
13
+ words.each { |w| root.add w.clone }
14
+ end
15
+
16
+ it 'generates a new root with the words from the passed root' do
17
+ new_root = compressor.compress root
18
+
19
+ expect(words).not_to be_empty
20
+ words.each do |word|
21
+ expect(new_root).to include word
22
+ end
23
+ end
24
+
25
+ it 'compresses the new root' do
26
+ new_root = compressor.compress root
27
+
28
+ expect(new_root.children_tree.keys).to eq %i(a few words hell)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,470 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Container do
4
+ let(:container) { Rambling::Trie::Container.new root, compressor }
5
+ let(:compressor) { Rambling::Trie::Compressor.new }
6
+ let(:root) { Rambling::Trie::RawNode.new }
7
+
8
+ describe '.new' do
9
+ context 'without a specified root' do
10
+ before do
11
+ allow(Rambling::Trie::RawNode).to receive(:new)
12
+ .and_return root
13
+ end
14
+
15
+ it 'initializes an empty trie root node' do
16
+ Rambling::Trie::Container.new
17
+ expect(Rambling::Trie::RawNode).to have_received :new
18
+ end
19
+ end
20
+
21
+ context 'without a specified compressor' do
22
+ before do
23
+ allow(Rambling::Trie::Compressor).to receive(:new)
24
+ .and_return compressor
25
+ end
26
+
27
+ it 'initializes a compressor' do
28
+ Rambling::Trie::Container.new
29
+ expect(Rambling::Trie::Compressor).to have_received :new
30
+ end
31
+ end
32
+
33
+ context 'with a block' do
34
+ it 'yields the container' do
35
+ yielded_container = nil
36
+
37
+ container = Rambling::Trie::Container.new root do |container|
38
+ yielded_container = container
39
+ end
40
+
41
+ expect(yielded_container).to eq container
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#add' do
47
+ let(:clone) { double :clone }
48
+ let(:word) { double :word, clone: clone }
49
+
50
+ before do
51
+ allow(root).to receive(:add)
52
+ end
53
+
54
+ it 'clones the original word' do
55
+ container.add word
56
+ expect(root).to have_received(:add).with clone
57
+ end
58
+ end
59
+
60
+ describe '#compress!' do
61
+ let(:node) { double :node, add: nil, compressed?: false }
62
+
63
+ before do
64
+ allow(compressor).to receive(:compress).and_return node
65
+ allow(root).to receive(:add)
66
+ end
67
+
68
+ it 'compresses the trie using the compressor' do
69
+ container.compress!
70
+
71
+ expect(compressor).to have_received(:compress)
72
+ .with root
73
+ end
74
+
75
+ it 'changes to the root returned by the compressor' do
76
+ container.compress!
77
+ container.add 'word'
78
+
79
+ expect(root).not_to have_received :add
80
+ expect(node).to have_received :add
81
+ end
82
+
83
+ it 'returns itself' do
84
+ expect(container.compress!).to eq container
85
+ end
86
+
87
+ it 'does not compress multiple times' do
88
+ container.compress!
89
+ allow(node).to receive(:compressed?).and_return(true)
90
+
91
+ container.compress!
92
+ expect(compressor).to have_received(:compress).once
93
+ end
94
+ end
95
+
96
+ describe '#word?' do
97
+ context 'for an uncompressed root' do
98
+ let(:root) do
99
+ double :root,
100
+ compressed?: false,
101
+ word?: nil
102
+ end
103
+
104
+ it 'calls the root with the word characters' do
105
+ container.word? 'words'
106
+ expect(root).to have_received(:word?).with %w(w o r d s)
107
+ end
108
+ end
109
+
110
+ context 'for a compressed root' do
111
+ let(:root) do
112
+ double :root,
113
+ compressed?: true,
114
+ word?: nil
115
+ end
116
+
117
+ it 'calls the root with the full word' do
118
+ container.word? 'words'
119
+ expect(root).to have_received(:word?).with %w(w o r d s)
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '#partial_word?' do
125
+ context 'for an uncompressed root' do
126
+ let(:root) do
127
+ double :root,
128
+ compressed?: false,
129
+ partial_word?: nil
130
+ end
131
+
132
+ it 'calls the root with the word characters' do
133
+ container.partial_word? 'words'
134
+ expect(root).to have_received(:partial_word?).with %w(w o r d s)
135
+ end
136
+ end
137
+
138
+ context 'for a compressed root' do
139
+ let(:root) do
140
+ double :root,
141
+ compressed?: true,
142
+ partial_word?: nil
143
+ end
144
+
145
+ it 'calls the root with the word characters' do
146
+ container.partial_word? 'words'
147
+ expect(root).to have_received(:partial_word?).with %w(w o r d s)
148
+ end
149
+ end
150
+ end
151
+
152
+ describe 'delegates and aliases' do
153
+ before do
154
+ allow(root).to receive_messages(
155
+ word?: nil,
156
+ partial_word?: nil,
157
+ scan: nil,
158
+ add: nil,
159
+ each: nil,
160
+ compressed?: nil
161
+ )
162
+ end
163
+
164
+ it 'aliases `#include?` to `#word?`' do
165
+ container.include? 'words'
166
+ expect(root).to have_received(:word?).with %w(w o r d s)
167
+ end
168
+
169
+ it 'aliases `#match?` to `#partial_word?`' do
170
+ container.match? 'words'
171
+ expect(root).to have_received(:partial_word?).with %w(w o r d s)
172
+ end
173
+
174
+ it 'aliases `#words` to `#scan`' do
175
+ container.words 'hig'
176
+ expect(root).to have_received(:scan).with %w(h i g)
177
+ end
178
+
179
+ it 'aliases `#<<` to `#add`' do
180
+ container << 'words'
181
+ expect(root).to have_received(:add).with 'words'
182
+ end
183
+
184
+ it 'delegates `#each` to the root node' do
185
+ container.each
186
+ expect(root).to have_received :each
187
+ end
188
+
189
+ it 'delegates `#compressed?` to the root node' do
190
+ container.compressed?
191
+ expect(root).to have_received :compressed?
192
+ end
193
+ end
194
+
195
+ describe '#compress!' do
196
+ let(:compressor) { Rambling::Trie::Compressor.new }
197
+ let(:root) { Rambling::Trie::RawNode.new }
198
+
199
+ context 'with at least one word' do
200
+ it 'keeps the root letter nil' do
201
+ container.add 'all'
202
+ container.compress!
203
+
204
+ expect(container.letter).to be_nil
205
+ end
206
+ end
207
+
208
+ context 'with a single word' do
209
+ before do
210
+ container.add 'all'
211
+ container.compress!
212
+ end
213
+
214
+ it 'compresses into a single node without children' do
215
+ expect(container[:all].letter).to eq :all
216
+ expect(container[:all].children.size).to eq 0
217
+ expect(container[:all]).to be_terminal
218
+ expect(container[:all]).to be_compressed
219
+ end
220
+ end
221
+
222
+ context 'with two words' do
223
+ before do
224
+ container.add 'all'
225
+ container.add 'ask'
226
+ container.compress!
227
+ end
228
+
229
+ it 'compresses into corresponding three nodes' do
230
+ expect(container[:a].letter).to eq :a
231
+ expect(container[:a].children.size).to eq 2
232
+
233
+ expect(container[:a][:ll].letter).to eq :ll
234
+ expect(container[:a][:sk].letter).to eq :sk
235
+
236
+ expect(container[:a][:ll].children.size).to eq 0
237
+ expect(container[:a][:sk].children.size).to eq 0
238
+
239
+ expect(container[:a][:ll]).to be_terminal
240
+ expect(container[:a][:sk]).to be_terminal
241
+
242
+ expect(container[:a][:ll]).to be_compressed
243
+ expect(container[:a][:sk]).to be_compressed
244
+ end
245
+ end
246
+
247
+ it 'reassigns the parent nodes correctly' do
248
+ container.add 'repay'
249
+ container.add 'rest'
250
+ container.add 'repaint'
251
+ container.compress!
252
+
253
+ expect(container[:re].letter).to eq :re
254
+ expect(container[:re].children.size).to eq 2
255
+
256
+ expect(container[:re][:pa].letter).to eq :pa
257
+ expect(container[:re][:st].letter).to eq :st
258
+
259
+ expect(container[:re][:pa].children.size).to eq 2
260
+ expect(container[:re][:st].children.size).to eq 0
261
+
262
+ expect(container[:re][:pa][:y].letter).to eq :y
263
+ expect(container[:re][:pa][:int].letter).to eq :int
264
+
265
+ expect(container[:re][:pa][:y].children.size).to eq 0
266
+ expect(container[:re][:pa][:int].children.size).to eq 0
267
+
268
+ expect(container[:re][:pa][:y].parent).to eq container[:re][:pa]
269
+ expect(container[:re][:pa][:int].parent).to eq container[:re][:pa]
270
+ end
271
+
272
+ it 'does not compress terminal nodes' do
273
+ container.add 'you'
274
+ container.add 'your'
275
+ container.add 'yours'
276
+
277
+ container.compress!
278
+
279
+ expect(container[:you].letter).to eq :you
280
+
281
+ expect(container[:you][:r].letter).to eq :r
282
+ expect(container[:you][:r]).to be_compressed
283
+
284
+ expect(container[:you][:r][:s].letter).to eq :s
285
+ expect(container[:you][:r][:s]).to be_compressed
286
+ end
287
+
288
+ describe 'and trying to add a word' do
289
+ it 'raises an error' do
290
+ container.add 'repay'
291
+ container.add 'rest'
292
+ container.add 'repaint'
293
+ container.compress!
294
+
295
+ expect { container.add 'restaurant' }.to raise_error Rambling::Trie::InvalidOperation
296
+ end
297
+ end
298
+ end
299
+
300
+ describe '#word?' do
301
+ let(:compressor) { Rambling::Trie::Compressor.new }
302
+ let(:root) { Rambling::Trie::RawNode.new }
303
+
304
+ context 'word is contained' do
305
+ before do
306
+ container.add 'hello'
307
+ container.add 'high'
308
+ end
309
+
310
+ it 'matches the whole word' do
311
+ expect(container.word? 'hello').to be true
312
+ expect(container.word? 'high').to be true
313
+ end
314
+
315
+ context 'and the root has been compressed' do
316
+ before do
317
+ container.compress!
318
+ end
319
+
320
+ it 'matches the whole word' do
321
+ expect(container.word? 'hello').to be true
322
+ expect(container.word? 'high').to be true
323
+ end
324
+ end
325
+ end
326
+
327
+ context 'word is not contained' do
328
+ before do
329
+ container.add 'hello'
330
+ end
331
+
332
+ it 'does not match the whole word' do
333
+ expect(container.word? 'halt').to be false
334
+ expect(container.word? 'al').to be false
335
+ end
336
+
337
+ context 'and the root has been compressed' do
338
+ before do
339
+ container.compress!
340
+ end
341
+
342
+ it 'does not match the whole word' do
343
+ expect(container.word? 'halt').to be false
344
+ expect(container.word? 'al').to be false
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ describe '#partial_word?' do
351
+ context 'word is contained' do
352
+ before do
353
+ container.add 'hello'
354
+ container.add 'high'
355
+ end
356
+
357
+ it 'matches part of the word' do
358
+ expect(container.partial_word? 'hell').to be true
359
+ expect(container.partial_word? 'hig').to be true
360
+ end
361
+
362
+ context 'and the root has been compressed' do
363
+ before do
364
+ container.compress!
365
+ end
366
+
367
+ it 'matches part of the word' do
368
+ expect(container.partial_word? 'h').to be true
369
+ expect(container.partial_word? 'he').to be true
370
+ expect(container.partial_word? 'hell').to be true
371
+ expect(container.partial_word? 'hello').to be true
372
+ expect(container.partial_word? 'hi').to be true
373
+ expect(container.partial_word? 'hig').to be true
374
+ expect(container.partial_word? 'high').to be true
375
+ end
376
+ end
377
+ end
378
+
379
+ context 'word is not contained' do
380
+ before do
381
+ container.add 'hello'
382
+ end
383
+
384
+ it 'does not match any part of the word' do
385
+ expect(container.partial_word? 'ha').to be false
386
+ expect(container.partial_word? 'hal').to be false
387
+ expect(container.partial_word? 'al').to be false
388
+ end
389
+
390
+ context 'and the root has been compressed' do
391
+ before do
392
+ container.compress!
393
+ end
394
+
395
+ it 'does not match any part of the word' do
396
+ expect(container.partial_word? 'ha').to be false
397
+ expect(container.partial_word? 'hal').to be false
398
+ expect(container.partial_word? 'al').to be false
399
+ end
400
+ end
401
+ end
402
+ end
403
+
404
+ describe '#scan' do
405
+ context 'words that match are not contained' do
406
+ before do
407
+ container.add 'hi'
408
+ container.add 'hello'
409
+ container.add 'high'
410
+ container.add 'hell'
411
+ container.add 'highlight'
412
+ container.add 'histerical'
413
+ end
414
+
415
+ it 'returns an array with the words that match' do
416
+ expect(container.scan 'hi').to eq [
417
+ 'hi',
418
+ 'high',
419
+ 'highlight',
420
+ 'histerical'
421
+ ]
422
+
423
+ expect(container.scan 'hig').to eq [
424
+ 'high',
425
+ 'highlight'
426
+ ]
427
+ end
428
+
429
+ context 'and the root has been compressed' do
430
+ before do
431
+ container.compress!
432
+ end
433
+
434
+ it 'returns an array with the words that match' do
435
+ expect(container.scan 'hi').to eq [
436
+ 'hi',
437
+ 'high',
438
+ 'highlight',
439
+ 'histerical'
440
+ ]
441
+
442
+ expect(container.scan 'hig').to eq [
443
+ 'high',
444
+ 'highlight'
445
+ ]
446
+ end
447
+ end
448
+ end
449
+
450
+ context 'words that match are not contained' do
451
+ before do
452
+ container.add 'hello'
453
+ end
454
+
455
+ it 'returns an empty array' do
456
+ expect(container.scan 'hi').to eq []
457
+ end
458
+
459
+ context 'and the root has been compressed' do
460
+ before do
461
+ container.compress!
462
+ end
463
+
464
+ it 'returns an empty array' do
465
+ expect(container.scan 'hi').to eq []
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end