rambling-trie 0.9.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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