rambling-trie 0.9.3 → 1.0.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/LICENSE +1 -1
  4. data/README.md +133 -26
  5. data/Rakefile +1 -2
  6. data/lib/rambling/trie.rb +53 -9
  7. data/lib/rambling/trie/comparable.rb +16 -0
  8. data/lib/rambling/trie/compressable.rb +14 -0
  9. data/lib/rambling/trie/compressed_node.rb +38 -14
  10. data/lib/rambling/trie/compressor.rb +14 -10
  11. data/lib/rambling/trie/configuration.rb +11 -0
  12. data/lib/rambling/trie/configuration/properties.rb +66 -0
  13. data/lib/rambling/trie/configuration/provider_collection.rb +101 -0
  14. data/lib/rambling/trie/container.rb +57 -17
  15. data/lib/rambling/trie/enumerable.rb +1 -1
  16. data/lib/rambling/trie/forwardable.rb +9 -4
  17. data/lib/rambling/trie/inspectable.rb +37 -0
  18. data/lib/rambling/trie/invalid_operation.rb +3 -2
  19. data/lib/rambling/trie/missing_node.rb +2 -1
  20. data/lib/rambling/trie/node.rb +40 -30
  21. data/lib/rambling/trie/raw_node.rb +29 -13
  22. data/lib/rambling/trie/readers.rb +11 -0
  23. data/lib/rambling/trie/readers/plain_text.rb +26 -0
  24. data/lib/rambling/trie/serializers.rb +11 -0
  25. data/lib/rambling/trie/serializers/file.rb +25 -0
  26. data/lib/rambling/trie/serializers/marshal.rb +38 -0
  27. data/lib/rambling/trie/serializers/yaml.rb +39 -0
  28. data/lib/rambling/trie/serializers/zip.rb +67 -0
  29. data/lib/rambling/trie/stringifyable.rb +20 -0
  30. data/lib/rambling/trie/version.rb +1 -1
  31. data/rambling-trie.gemspec +2 -2
  32. data/spec/integration/rambling/trie_spec.rb +45 -49
  33. data/spec/lib/rambling/trie/comparable_spec.rb +104 -0
  34. data/spec/lib/rambling/trie/compressed_node_spec.rb +44 -0
  35. data/spec/lib/rambling/trie/configuration/properties_spec.rb +49 -0
  36. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +165 -0
  37. data/spec/lib/rambling/trie/container_spec.rb +127 -38
  38. data/spec/lib/rambling/trie/{inspector_spec.rb → inspectable_spec.rb} +7 -5
  39. data/spec/lib/rambling/trie/raw_node_spec.rb +22 -41
  40. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +14 -0
  41. data/spec/lib/rambling/trie/serializers/file_spec.rb +11 -0
  42. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +14 -0
  43. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +14 -0
  44. data/spec/lib/rambling/trie/serializers/zip_spec.rb +30 -0
  45. data/spec/lib/rambling/trie/stringifyable_spec.rb +82 -0
  46. data/spec/lib/rambling/trie_spec.rb +120 -7
  47. data/spec/spec_helper.rb +7 -1
  48. data/spec/support/config.rb +5 -0
  49. data/spec/support/shared_examples/a_compressable_trie.rb +26 -0
  50. data/spec/support/shared_examples/a_serializable_trie.rb +26 -0
  51. data/spec/support/shared_examples/a_serializer.rb +29 -0
  52. data/spec/support/shared_examples/a_trie_data_structure.rb +29 -0
  53. data/spec/tmp/.gitkeep +0 -0
  54. metadata +51 -24
  55. data/lib/rambling/trie/compression.rb +0 -13
  56. data/lib/rambling/trie/inspector.rb +0 -11
  57. data/lib/rambling/trie/plain_text_reader.rb +0 -23
  58. data/lib/rambling/trie/tasks/gem.rb +0 -17
  59. data/lib/rambling/trie/tasks/helpers/path.rb +0 -17
  60. data/lib/rambling/trie/tasks/helpers/performance_report.rb +0 -17
  61. data/lib/rambling/trie/tasks/helpers/time.rb +0 -7
  62. data/lib/rambling/trie/tasks/performance.rb +0 -15
  63. data/lib/rambling/trie/tasks/performance/all.rb +0 -17
  64. data/lib/rambling/trie/tasks/performance/benchmark.rb +0 -201
  65. data/lib/rambling/trie/tasks/performance/directory.rb +0 -11
  66. data/lib/rambling/trie/tasks/performance/flamegraph.rb +0 -119
  67. data/lib/rambling/trie/tasks/performance/profile/call_tree.rb +0 -147
  68. data/lib/rambling/trie/tasks/performance/profile/memory.rb +0 -143
  69. data/spec/lib/rambling/trie/plain_text_reader_spec.rb +0 -18
@@ -6,49 +6,23 @@ describe Rambling::Trie::Container do
6
6
  let(:root) { Rambling::Trie::RawNode.new }
7
7
 
8
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
9
+ it 'uses the provided node as root' do
10
+ expect(container.root).to be root
31
11
  end
32
12
 
33
13
  context 'with a block' do
34
14
  it 'yields the container' do
35
- yielded_container = nil
15
+ yielded = nil
36
16
 
37
- container = Rambling::Trie::Container.new root do |container|
38
- yielded_container = container
17
+ container = Rambling::Trie::Container.new root, compressor do |container|
18
+ yielded = container
39
19
  end
40
20
 
41
- expect(yielded_container).to eq container
21
+ expect(yielded).to be container
42
22
  end
43
23
  end
44
24
  end
45
25
 
46
- describe '#root' do
47
- it 'returns the trie root node' do
48
- expect(container.root).to eq root
49
- end
50
- end
51
-
52
26
  describe '#add' do
53
27
  let(:clone) { double :clone }
54
28
  let(:word) { double :word, clone: clone }
@@ -470,12 +444,9 @@ describe Rambling::Trie::Container do
470
444
  describe '#scan' do
471
445
  context 'words that match are not contained' do
472
446
  before do
473
- container.add 'hi'
474
- container.add 'hello'
475
- container.add 'high'
476
- container.add 'hell'
477
- container.add 'highlight'
478
- container.add 'histerical'
447
+ %w(hi hello high hell highlight histerical).each do |word|
448
+ container.add word
449
+ end
479
450
  end
480
451
 
481
452
  it 'returns an array with the words that match' do
@@ -533,4 +504,122 @@ describe Rambling::Trie::Container do
533
504
  end
534
505
  end
535
506
  end
507
+
508
+ describe '#words_within' do
509
+ before do
510
+ %w(one word and other words).each do |word|
511
+ container.add word
512
+ end
513
+ end
514
+
515
+ context 'phrase does not contain any words' do
516
+ it 'returns an empty array' do
517
+ expect(container.words_within 'xyz').to match_array []
518
+ end
519
+
520
+ context 'and the node is compressed' do
521
+ before do
522
+ container.compress!
523
+ end
524
+
525
+ it 'returns an empty array' do
526
+ expect(container.words_within 'xyz').to match_array []
527
+ end
528
+ end
529
+ end
530
+
531
+ context 'phrase contains one word at the start of the phrase' do
532
+ it 'returns an array with the word found in the phrase' do
533
+ expect(container.words_within 'word').to match_array %w(word)
534
+ expect(container.words_within 'wordxyz').to match_array %w(word)
535
+ end
536
+
537
+ context 'and the node is compressed' do
538
+ before do
539
+ container.compress!
540
+ end
541
+
542
+ it 'returns an array with the word found in the phrase' do
543
+ expect(container.words_within 'word').to match_array %w(word)
544
+ expect(container.words_within 'wordxyz').to match_array %w(word)
545
+ end
546
+ end
547
+ end
548
+
549
+ context 'phrase contains one word at the end of the phrase' do
550
+ it 'returns an array with the word found in the phrase' do
551
+ expect(container.words_within 'xyz word').to match_array %w(word)
552
+ end
553
+
554
+ context 'and the node is compressed' do
555
+ before do
556
+ container.compress!
557
+ end
558
+
559
+ it 'returns an array with the word found in the phrase' do
560
+ expect(container.words_within 'xyz word').to match_array %w(word)
561
+ end
562
+ end
563
+ end
564
+
565
+ context 'phrase contains a few words' do
566
+ it 'returns an array with all words found in the phrase' do
567
+ expect(container.words_within 'xyzword otherzxyone').to match_array %w(word other one)
568
+ end
569
+
570
+ context 'and the node is compressed' do
571
+ before do
572
+ container.compress!
573
+ end
574
+
575
+ it 'returns an array with all words found in the phrase' do
576
+ expect(container.words_within 'xyzword otherzxyone').to match_array %w(word other one)
577
+ end
578
+ end
579
+ end
580
+ end
581
+
582
+ describe '#words_within?' do
583
+ before do
584
+ %w(one word and other words).each do |word|
585
+ container.add word
586
+ end
587
+ end
588
+
589
+ context 'phrase does not contain any words' do
590
+ it 'returns false' do
591
+ expect(container.words_within? 'xyz').to be false
592
+ end
593
+ end
594
+
595
+ context 'phrase contains any word' do
596
+ it 'returns true' do
597
+ expect(container.words_within? 'xyz words').to be true
598
+ expect(container.words_within? 'xyzone word').to be true
599
+ end
600
+ end
601
+ end
602
+
603
+ describe '#==' do
604
+ context 'when the root nodes are the same' do
605
+ let(:other_container) { Rambling::Trie::Container.new container.root, compressor }
606
+
607
+ it 'returns true' do
608
+ expect(container).to eq other_container
609
+ end
610
+ end
611
+
612
+ context 'when the root nodes are not the same' do
613
+ let(:other_root) { Rambling::Trie::RawNode.new }
614
+ let(:other_container) do
615
+ Rambling::Trie::Container.new other_root, compressor do |c|
616
+ c << 'hola'
617
+ end
618
+ end
619
+
620
+ it 'returns false' do
621
+ expect(container).not_to eq other_container
622
+ end
623
+ end
624
+ end
536
625
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Rambling::Trie::Inspector do
3
+ describe Rambling::Trie::Inspectable do
4
4
  let(:root) { Rambling::Trie::RawNode.new }
5
5
 
6
6
  before do
@@ -9,10 +9,12 @@ describe Rambling::Trie::Inspector do
9
9
 
10
10
  describe '#inspect' do
11
11
  let(:node) { root[:o] }
12
+ let(:terminal_node) { root[:o][:n][:l][:y] }
12
13
 
13
14
  it 'returns a pretty printed version of the node' do
14
- expect(root.inspect).to eq "#<Rambling::Trie::RawNode letter: nil, children: [:o, :t, :w]>"
15
- expect(node.inspect).to eq "#<Rambling::Trie::RawNode letter: :o, children: [:n]>"
15
+ expect(root.inspect).to eq "#<Rambling::Trie::RawNode letter: nil, terminal: nil, children: [:o, :t, :w]>"
16
+ expect(node.inspect).to eq "#<Rambling::Trie::RawNode letter: :o, terminal: nil, children: [:n]>"
17
+ expect(terminal_node.inspect).to eq "#<Rambling::Trie::RawNode letter: :y, terminal: true, children: []>"
16
18
  end
17
19
 
18
20
  context 'for a compressed node' do
@@ -21,8 +23,8 @@ describe Rambling::Trie::Inspector do
21
23
  let(:compressed_node) { compressed_root[:only] }
22
24
 
23
25
  it 'returns a pretty printed version of the compressed node' do
24
- expect(compressed_root.inspect).to eq "#<Rambling::Trie::CompressedNode letter: nil, children: [:only, :three, :words]>"
25
- expect(compressed_node.inspect).to eq "#<Rambling::Trie::CompressedNode letter: :only, children: []>"
26
+ expect(compressed_root.inspect).to eq "#<Rambling::Trie::CompressedNode letter: nil, terminal: nil, children: [:only, :three, :words]>"
27
+ expect(compressed_node.inspect).to eq "#<Rambling::Trie::CompressedNode letter: :only, terminal: true, children: []>"
26
28
  end
27
29
  end
28
30
  end
@@ -347,61 +347,42 @@ describe Rambling::Trie::RawNode do
347
347
  end
348
348
  end
349
349
 
350
- describe '#as_word' do
351
- let(:node) { Rambling::Trie::RawNode.new }
352
-
353
- context 'for an empty node' do
354
- before do
355
- node.add ''
356
- end
357
-
358
- it 'returns nil' do
359
- expect(node.as_word).to be_empty
360
- end
350
+ describe '#match_prefix' do
351
+ before do
352
+ node.letter = :i
353
+ node.add 'gnite'
354
+ node.add 'mport'
355
+ node.add 'mportant'
356
+ node.add 'mportantly'
361
357
  end
362
358
 
363
- context 'for one letter' do
359
+ context 'when the node is terminal' do
364
360
  before do
365
- node.letter = :a
366
- node.add ''
361
+ node.terminal!
367
362
  end
368
363
 
369
- it 'returns the expected one letter word' do
370
- expect(node.as_word).to eq 'a'
364
+ it 'adds itself to the words' do
365
+ expect(node.match_prefix %w(g n i t e)).to include 'i'
371
366
  end
372
367
  end
373
368
 
374
- context 'for a small word' do
375
- before do
376
- node.letter = :a
377
- node.add 'll'
378
- end
379
-
380
- it 'returns the expected small word' do
381
- expect(node[:l][:l].as_word).to eq 'all'
382
- end
383
-
384
- it 'raises an error for a non terminal node' do
385
- expect { node[:l].as_word }.to raise_error Rambling::Trie::InvalidOperation
369
+ context 'when the node is not terminal' do
370
+ it 'does not add itself to the words' do
371
+ expect(node.match_prefix %w(g n i t e)).not_to include 'i'
386
372
  end
387
373
  end
388
374
 
389
- context 'for a long word' do
390
- before do
391
- node.letter = :b
392
- node.add 'eautiful'
393
- end
394
-
395
- it 'returns the expected long word' do
396
- expect(node[:e][:a][:u][:t][:i][:f][:u][:l].as_word).to eq 'beautiful'
375
+ context 'when the first few chars match a terminal node' do
376
+ it 'adds those terminal nodes to the words' do
377
+ words = node.match_prefix(%w(m p o r t a n t l y)).to_a
378
+ expect(words).to include 'import', 'important', 'importantly'
397
379
  end
398
380
  end
399
381
 
400
- context 'for a node with nil letter' do
401
- let(:node) { Rambling::Trie::RawNode.new nil }
402
-
403
- it 'returns nil' do
404
- expect(node.as_word).to be_empty
382
+ context 'when the first few chars do not match a terminal node' do
383
+ it 'does not add any other words found' do
384
+ words = node.match_prefix(%w(m p m p o r t a n t l y)).to_a
385
+ expect(words).not_to include 'import', 'important', 'importantly'
405
386
  end
406
387
  end
407
388
  end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Readers::PlainText do
4
+ describe '#each_word' do
5
+ let(:filepath) { File.join(::SPEC_ROOT, 'assets', 'test_words.en_US.txt') }
6
+ let(:words) { File.readlines(filepath).map &:chomp }
7
+
8
+ it 'yields every word yielded by the file' do
9
+ yielded = []
10
+ subject.each_word(filepath) { |word| yielded << word }
11
+ expect(yielded).to eq words
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Serializers::File do
4
+ let(:serializer) { Rambling::Trie::Serializers::File.new }
5
+
6
+ it_behaves_like 'a serializer' do
7
+ let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.file' }
8
+ let(:content) { 'a few words to validate that load and dump are working' }
9
+ let(:formatted_content) { content }
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Serializers::Marshal do
4
+ let(:serializer) { Rambling::Trie::Serializers::Marshal.new }
5
+
6
+ let(:words) { %w(a few words to validate that load and dump are working) }
7
+ let(:trie) { Rambling::Trie.create { |t| words.each { |w| t << w } } }
8
+
9
+ it_behaves_like 'a serializer' do
10
+ let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.marshal' }
11
+ let(:content) { trie.root }
12
+ let(:formatted_content) { Marshal.dump content }
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Serializers::Yaml do
4
+ let(:serializer) { Rambling::Trie::Serializers::Yaml.new }
5
+
6
+ let(:words) { %w(a few words to validate that load and dump are working) }
7
+ let(:trie) { Rambling::Trie.create { |t| words.each { |w| t << w } } }
8
+
9
+ it_behaves_like 'a serializer' do
10
+ let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.yml' }
11
+ let(:content) { trie.root }
12
+ let(:formatted_content) { YAML.dump content }
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Serializers::Zip do
4
+ let(:properties) { Rambling::Trie::Configuration::Properties.new }
5
+ let(:serializer) { Rambling::Trie::Serializers::Zip.new properties }
6
+
7
+ let(:words) { %w(a few words to validate that load and dump are working) }
8
+ let(:trie) { Rambling::Trie.create { |t| words.each { |w| t << w } } }
9
+ let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
10
+
11
+ before do
12
+ properties.tmp_path = tmp_path
13
+ end
14
+
15
+ it_behaves_like 'a serializer' do
16
+ let(:filename) { 'trie-root.marshal' }
17
+ let(:filepath) { File.join tmp_path, "#{filename}.zip" }
18
+ let(:content) { trie.root }
19
+ let(:formatted_content) { zip Marshal.dump content }
20
+ end
21
+
22
+ def zip content
23
+ io = Zip::OutputStream.write_buffer do |io|
24
+ io.put_next_entry filename
25
+ io.write content
26
+ end
27
+ io.rewind
28
+ io.read
29
+ end
30
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Stringifyable do
4
+ describe '#as_word' do
5
+ let(:node) { Rambling::Trie::RawNode.new }
6
+
7
+ context 'for an empty node' do
8
+ before do
9
+ node.add ''
10
+ end
11
+
12
+ it 'returns nil' do
13
+ expect(node.as_word).to be_empty
14
+ end
15
+ end
16
+
17
+ context 'for one letter' do
18
+ before do
19
+ node.letter = :a
20
+ node.add ''
21
+ end
22
+
23
+ it 'returns the expected one letter word' do
24
+ expect(node.as_word).to eq 'a'
25
+ end
26
+ end
27
+
28
+ context 'for a small word' do
29
+ before do
30
+ node.letter = :a
31
+ node.add 'll'
32
+ end
33
+
34
+ it 'returns the expected small word' do
35
+ expect(node[:l][:l].as_word).to eq 'all'
36
+ end
37
+
38
+ it 'raises an error for a non terminal node' do
39
+ expect { node[:l].as_word }.to raise_error Rambling::Trie::InvalidOperation
40
+ end
41
+ end
42
+
43
+ context 'for a long word' do
44
+ before do
45
+ node.letter = :b
46
+ node.add 'eautiful'
47
+ end
48
+
49
+ it 'returns the expected long word' do
50
+ expect(node[:e][:a][:u][:t][:i][:f][:u][:l].as_word).to eq 'beautiful'
51
+ end
52
+ end
53
+
54
+ context 'for a node with nil letter' do
55
+ let(:node) { Rambling::Trie::RawNode.new nil }
56
+
57
+ it 'returns nil' do
58
+ expect(node.as_word).to be_empty
59
+ end
60
+ end
61
+
62
+ context 'for a compressed node' do
63
+ let(:compressor) { Rambling::Trie::Compressor.new }
64
+ let(:compressed_node) { compressor.compress node }
65
+
66
+ before do
67
+ node.letter = :a
68
+ node.add 'm'
69
+ node.add 'dd'
70
+ end
71
+
72
+ it 'returns the words for the terminal nodes' do
73
+ expect(compressed_node[:m].as_word).to eq 'am'
74
+ expect(compressed_node[:dd].as_word).to eq 'add'
75
+ end
76
+
77
+ it 'raise an error for non terminal nodes' do
78
+ expect { compressed_node.as_word }.to raise_error Rambling::Trie::InvalidOperation
79
+ end
80
+ end
81
+ end
82
+ end