rambling-trie 2.4.0 → 2.5.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +16 -10
  4. data/lib/rambling/trie/compressor.rb +2 -0
  5. data/lib/rambling/trie/configuration/properties.rb +5 -2
  6. data/lib/rambling/trie/configuration/provider_collection.rb +2 -2
  7. data/lib/rambling/trie/container.rb +12 -1
  8. data/lib/rambling/trie/enumerable.rb +1 -1
  9. data/lib/rambling/trie/nodes/node.rb +4 -4
  10. data/lib/rambling/trie/nodes/raw.rb +2 -1
  11. data/lib/rambling/trie/readers/plain_text.rb +1 -1
  12. data/lib/rambling/trie/serializers/file.rb +1 -3
  13. data/lib/rambling/trie/serializers/marshal.rb +3 -3
  14. data/lib/rambling/trie/serializers/yaml.rb +2 -2
  15. data/lib/rambling/trie/serializers/zip.rb +2 -0
  16. data/lib/rambling/trie/version.rb +1 -1
  17. data/lib/rambling/trie.rb +1 -1
  18. data/rambling-trie.gemspec +3 -9
  19. metadata +7 -122
  20. data/spec/assets/test_words.en_US.txt +0 -23
  21. data/spec/assets/test_words.es_DO.txt +0 -24
  22. data/spec/integration/rambling/trie_spec.rb +0 -116
  23. data/spec/lib/rambling/trie/comparable_spec.rb +0 -87
  24. data/spec/lib/rambling/trie/compressor_spec.rb +0 -111
  25. data/spec/lib/rambling/trie/configuration/properties_spec.rb +0 -75
  26. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +0 -177
  27. data/spec/lib/rambling/trie/container_spec.rb +0 -466
  28. data/spec/lib/rambling/trie/enumerable_spec.rb +0 -50
  29. data/spec/lib/rambling/trie/inspectable_spec.rb +0 -62
  30. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +0 -43
  31. data/spec/lib/rambling/trie/nodes/node_spec.rb +0 -9
  32. data/spec/lib/rambling/trie/nodes/raw_spec.rb +0 -184
  33. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +0 -26
  34. data/spec/lib/rambling/trie/readers/reader_spec.rb +0 -14
  35. data/spec/lib/rambling/trie/serializers/file_spec.rb +0 -11
  36. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +0 -10
  37. data/spec/lib/rambling/trie/serializers/serializer_spec.rb +0 -21
  38. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +0 -10
  39. data/spec/lib/rambling/trie/serializers/zip_spec.rb +0 -36
  40. data/spec/lib/rambling/trie/stringifyable_spec.rb +0 -89
  41. data/spec/lib/rambling/trie_spec.rb +0 -244
  42. data/spec/spec_helper.rb +0 -42
  43. data/spec/support/config.rb +0 -15
  44. data/spec/support/helpers/add_word.rb +0 -20
  45. data/spec/support/helpers/one_line_heredoc.rb +0 -11
  46. data/spec/support/shared_examples/a_compressible_trie.rb +0 -46
  47. data/spec/support/shared_examples/a_container_partial_word.rb +0 -17
  48. data/spec/support/shared_examples/a_container_scan.rb +0 -14
  49. data/spec/support/shared_examples/a_container_word.rb +0 -43
  50. data/spec/support/shared_examples/a_container_words_within.rb +0 -44
  51. data/spec/support/shared_examples/a_serializable_trie.rb +0 -26
  52. data/spec/support/shared_examples/a_serializer.rb +0 -60
  53. data/spec/support/shared_examples/a_trie_data_structure.rb +0 -45
  54. data/spec/support/shared_examples/a_trie_node.rb +0 -135
  55. data/spec/support/shared_examples/a_trie_node_implementation.rb +0 -149
  56. data/spec/tmp/.gitkeep +0 -0
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'helpers/add_word'
4
- require_relative 'helpers/one_line_heredoc'
5
-
6
- RSpec.configure do |c|
7
- c.before do
8
- Rambling::Trie.config.reset
9
- end
10
-
11
- c.include Support::Helpers::AddWord
12
- c.include Support::Helpers::OneLineHeredoc
13
-
14
- RSpec::Matchers.define_negated_matcher :not_change, :change
15
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Support
4
- module Helpers
5
- module AddWord
6
- def add_words node, words
7
- words.each { |word| add_word node, word }
8
- end
9
-
10
- def add_word node, word
11
- case node
12
- when Rambling::Trie::Container
13
- node.add word
14
- else
15
- node.add word.chars.reverse.map(&:to_sym)
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Support
4
- module Helpers
5
- module OneLineHeredoc
6
- def one_line heredoc
7
- heredoc.strip.tr "\n", ' '
8
- end
9
- end
10
- end
11
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a compressible trie' do
4
- context 'with an uncompressed trie' do
5
- it_behaves_like 'a trie data structure'
6
-
7
- it 'does not alter the input' do
8
- word = 'string'
9
- add_word trie, word
10
-
11
- expect(word).to eq 'string'
12
- end
13
-
14
- it 'is marked as not compressed' do
15
- expect(trie).not_to be_compressed
16
- end
17
- end
18
-
19
- context 'with an compressed trie' do
20
- let!(:original_root) { trie.root }
21
- let!(:original_keys) { original_root.children_tree.keys }
22
- let!(:original_values) { original_root.children_tree.values }
23
-
24
- before do
25
- trie.compress!
26
- end
27
-
28
- it_behaves_like 'a trie data structure'
29
-
30
- it 'is marked as compressed' do
31
- expect(trie).to be_compressed
32
- end
33
-
34
- it 'leaves the original root keys intact' do
35
- expect(original_root.children_tree.keys).to eq original_keys
36
- end
37
-
38
- it 'leaves the original trie keys intact' do
39
- expect(trie.children_tree.keys).to eq original_keys
40
- end
41
-
42
- it 'leaves the original trie values intact' do
43
- expect(trie.children_tree.values).not_to eq original_values
44
- end
45
- end
46
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a matching container#partial_word' do
4
- %w(h he hell hello hi hig high).each do |prefix|
5
- it 'matches part of the word' do
6
- expect(container.partial_word? prefix).to be true
7
- end
8
- end
9
- end
10
-
11
- shared_examples_for 'a non-matching container#partial_word' do
12
- it 'does not match any part of the word' do
13
- %w(ha hal al).each do |word|
14
- expect(container.partial_word? word).to be false
15
- end
16
- end
17
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a matching container#scan' do
4
- [
5
- ['hi', %w(hi high highlight histerical)],
6
- ['hig', %w(high highlight)],
7
- ].each do |test_params|
8
- prefix, expected = test_params
9
-
10
- it "returns an array with the words that match '#{prefix}'" do
11
- expect(container.scan prefix).to eq expected
12
- end
13
- end
14
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a matching container#word' do
4
- %w(hello high).each do |word|
5
- it 'matches the whole word' do
6
- expect(container.word? word).to be true
7
- end
8
- end
9
- end
10
-
11
- shared_examples_for 'a non-matching container#word' do
12
- %w(halt al).each do |word|
13
- it 'does not match the whole word' do
14
- expect(container.word? word).to be false
15
- end
16
- end
17
- end
18
-
19
- shared_examples_for 'a propagating node' do
20
- [
21
- [true, 'Rambling::Trie::Nodes::Compressed'],
22
- [false, 'Rambling::Trie::Nodes::Raw'],
23
- ].each do |test_params|
24
- compressed_value, instance_double_class = test_params
25
-
26
- context "when root has compressed=#{compressed_value}" do
27
- let :root do
28
- instance_double(
29
- instance_double_class,
30
- :root,
31
- compressed?: compressed_value,
32
- word?: nil,
33
- partial_word?: nil,
34
- )
35
- end
36
-
37
- it 'calls the root with the word characters' do
38
- container.public_send method_name, 'words'
39
- expect(root).to have_received(method_name).with %w(w o r d s)
40
- end
41
- end
42
- end
43
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a matching container#words_within' do
4
- [
5
- ['word', %w(word)],
6
- ['wordxyz', %w(word)],
7
- ].each do |test_params|
8
- phrase, expected = test_params
9
-
10
- it "returns an array with the word found in the phrase '#{phrase}'" do
11
- expect(container.words_within phrase).to match_array expected
12
- end
13
- end
14
- end
15
-
16
- shared_examples_for 'a non-matching container#words_within' do
17
- it 'returns an array with all words found in the phrase' do
18
- expect(container.words_within 'xyzword otherzxyone')
19
- .to match_array %w(word other one)
20
- end
21
- end
22
-
23
- shared_examples_for 'a matching container#words_within?' do
24
- context 'when phrase does not contain any words' do
25
- it 'returns false' do
26
- expect(container.words_within? 'xyz').to be false
27
- end
28
- end
29
-
30
- context 'when phrase contains any word' do
31
- ['xyz words', 'xyzone word'].each do |phrase|
32
- it "returns true for '#{phrase}'" do
33
- expect(container.words_within? phrase).to be true
34
- end
35
- end
36
- end
37
- end
38
-
39
- shared_examples_for 'a non-matching container#words_within?' do
40
- it 'returns an array with all words found in the phrase' do
41
- expect(container.words_within 'xyzword otherzxyone')
42
- .to match_array %w(word other one)
43
- end
44
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a serializable trie' do
4
- let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
5
- let(:filepath) { File.join tmp_path, "trie-root.#{file_format}" }
6
-
7
- context 'with an uncompressed trie' do
8
- before { Rambling::Trie.dump trie_to_serialize, filepath }
9
-
10
- it_behaves_like 'a compressible trie' do
11
- let(:trie) { Rambling::Trie.load filepath }
12
- end
13
- end
14
-
15
- context 'with an compressed trie' do
16
- let(:trie) { Rambling::Trie.load filepath }
17
-
18
- before { Rambling::Trie.dump trie_to_serialize.compress!, filepath }
19
-
20
- it_behaves_like 'a trie data structure'
21
-
22
- it 'is marked as compressed' do
23
- expect(trie).to be_compressed
24
- end
25
- end
26
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a serializer' do
4
- subject(:serializer) { described_class.new }
5
-
6
- let(:trie) { Rambling::Trie.create }
7
- let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
8
- let(:filepath) { File.join tmp_path, "trie-root.#{file_format}" }
9
- let(:content) { trie.root }
10
-
11
- before do
12
- trie.concat %w(a few words to validate that load and dump are working)
13
- FileUtils.rm_f filepath
14
- end
15
-
16
- describe '#dump' do
17
- [true, false].each do |compress_value|
18
- context "with compressed=#{compress_value} trie" do
19
- let(:formatted_content) { format_content.call content }
20
-
21
- before { trie.compress! if compress_value }
22
-
23
- it 'returns the size in bytes of the file dumped' do
24
- total_bytes = serializer.dump content, filepath
25
- expect(total_bytes).to be_within(20).of formatted_content.size
26
- end
27
-
28
- it 'creates the file with the provided path' do
29
- serializer.dump content, filepath
30
- expect(File.exist? filepath).to be true
31
- end
32
-
33
- it 'converts the contents to the appropriate format' do
34
- serializer.dump content, filepath
35
- expect(File.size filepath).to be_within(20).of formatted_content.size
36
- end
37
- end
38
- end
39
- end
40
-
41
- describe '#load' do
42
- [true, false].each do |compress_value|
43
- context "with compressed=#{compress_value} trie" do
44
- before do
45
- trie.compress! if compress_value
46
- serializer.dump content, filepath
47
- end
48
-
49
- it 'loads the dumped object back into memory' do
50
- expect(serializer.load filepath).to eq content
51
- end
52
-
53
- it "loads a compressed=#{compress_value} object" do
54
- loaded = serializer.load filepath
55
- expect(loaded.compressed?).to be compress_value unless :file == file_format
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a trie data structure' do
4
- it 'contains all the words previously provided' do
5
- words.each { |word| expect(trie).to include word }
6
- end
7
-
8
- it 'returns true for #word? for all words previously provided' do
9
- words.each { |word| expect(trie.word? word).to be true }
10
- end
11
-
12
- it 'matches the full word for all words in file' do
13
- words.each { |word| expect(trie.match? word).to be true }
14
- end
15
-
16
- it 'matches the start of all the words in file' do
17
- words.each { |word| expect(trie.match? word[0..-2]).to be true }
18
- end
19
-
20
- it 'returns true for #partial_word? with full word for all words in file' do
21
- words.each { |word| expect(trie.partial_word? word).to be true }
22
- end
23
-
24
- it 'returns true for #partial_word? with the start of all words in file' do
25
- words.each { |word| expect(trie.partial_word? word[0..-2]).to be true }
26
- end
27
-
28
- it 'extracts words within larger strings' do
29
- words.each do |word|
30
- phrase = "x#{word}y"
31
- expect(trie.words_within phrase).to include word
32
- end
33
- end
34
-
35
- it 'identifies words within larger strings' do
36
- words.each do |word|
37
- phrase = "x#{word}y"
38
- expect(trie.words_within? phrase).to be true
39
- end
40
- end
41
-
42
- it 'allows iterating over all the words' do
43
- expect(trie.to_a.sort).to eq words.sort
44
- end
45
- end
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a trie node' do
4
- let(:node_class) { node.class }
5
-
6
- describe '.new' do
7
- it 'has no letter' do
8
- expect(node.letter).to be_nil
9
- end
10
-
11
- it 'has no children' do
12
- expect(node.children.size).to eq 0
13
- end
14
-
15
- it 'is not terminal' do
16
- expect(node).not_to be_terminal
17
- end
18
-
19
- it 'returns empty string as its word' do
20
- expect(node.as_word).to be_empty
21
- end
22
-
23
- context 'with a letter and a parent' do
24
- let(:parent) { node_class.new }
25
- # noinspection RubyArgCount
26
- let(:node_with_parent) { node_class.new :a, parent }
27
-
28
- it 'does not have any letter' do
29
- expect(node_with_parent.letter).to eq :a
30
- end
31
-
32
- it 'has no children' do
33
- expect(node_with_parent.children.size).to eq 0
34
- end
35
-
36
- it 'is not terminal' do
37
- expect(node_with_parent).not_to be_terminal
38
- end
39
- end
40
- end
41
-
42
- describe '#root?' do
43
- context 'when the node has a parent' do
44
- before { node.parent = node }
45
-
46
- it 'returns false' do
47
- expect(node).not_to be_root
48
- end
49
- end
50
-
51
- context 'when the node does not have a parent' do
52
- before { node.parent = nil }
53
-
54
- it 'returns true' do
55
- expect(node).to be_root
56
- end
57
- end
58
- end
59
-
60
- describe '#terminal!' do
61
- # rubocop:disable RSpec/MultipleExpectations
62
- it 'forces the node to be terminal' do
63
- expect(node).not_to be_terminal
64
- node.terminal!
65
- expect(node).to be_terminal
66
- end
67
- # rubocop:enable RSpec/MultipleExpectations
68
-
69
- it 'returns the node' do
70
- expect(node.terminal!).to eq node
71
- end
72
- end
73
-
74
- describe 'delegates and aliases' do
75
- let :children_tree do
76
- instance_double(
77
- 'Hash',
78
- :children_tree,
79
- :[] => 'value',
80
- :[]= => nil,
81
- key?: false,
82
- delete: true,
83
- )
84
- end
85
-
86
- before { node.children_tree = children_tree }
87
-
88
- # rubocop:disable RSpec/MultipleExpectations
89
- it 'delegates `#[]` to its children tree' do
90
- expect(node[:key]).to eq 'value'
91
- expect(children_tree).to have_received(:[]).with :key
92
- end
93
- # rubocop:enable RSpec/MultipleExpectations
94
-
95
- it 'delegates `#[]=` to its children tree' do
96
- node[:key] = 'value'
97
- expect(children_tree).to have_received(:[]=).with(:key, 'value')
98
- end
99
-
100
- # rubocop:disable RSpec/MultipleExpectations
101
- it 'delegates `#key?` to its children tree' do
102
- allow(children_tree).to receive(:key?)
103
- .with(:present_key)
104
- .and_return true
105
-
106
- expect(node).to have_key(:present_key)
107
- expect(node).not_to have_key(:absent_key)
108
- end
109
- # rubocop:enable RSpec/MultipleExpectations
110
-
111
- # rubocop:disable RSpec/MultipleExpectations
112
- it 'delegates `#delete` to its children tree' do
113
- expect(node.delete :key).to be true
114
- expect(children_tree).to have_received(:delete).with :key
115
- end
116
- # rubocop:enable RSpec/MultipleExpectations
117
-
118
- # rubocop:disable RSpec/ExampleLength
119
- it 'delegates `#children` to its children tree values' do
120
- children = [
121
- instance_double('Rambling::Trie::Nodes::Node', :child_one),
122
- instance_double('Rambling::Trie::Nodes::Node', :child_two),
123
- ]
124
- allow(children_tree).to receive(:values).and_return children
125
-
126
- expect(node.children).to eq children
127
- end
128
- # rubocop:enable RSpec/ExampleLength
129
-
130
- it 'aliases `#has_key?` to `#key?`' do
131
- node.has_key? :nope
132
- expect(children_tree).to have_received(:key?).with :nope
133
- end
134
- end
135
- end
@@ -1,149 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for 'a trie node implementation' do
4
- it_behaves_like 'a trie node'
5
-
6
- describe '#partial_word?' do
7
- context 'when the chars array is empty' do
8
- it 'returns true' do
9
- expect(node.partial_word? []).to be true
10
- end
11
- end
12
-
13
- context 'when the chars array is not empty' do
14
- context 'when the node has a tree that matches the characters' do
15
- before { add_word_to_tree 'abc' }
16
-
17
- [%w(a), %w(a b), %w(a b c)].each do |letters|
18
- it "returns true for '#{letters}'" do
19
- expect(node.partial_word? letters).to be true
20
- end
21
- end
22
- end
23
-
24
- context 'when the node has a tree that does not match the characters' do
25
- before { add_word_to_tree 'cba' }
26
-
27
- [%w(a), %w(a b), %w(a b c)].each do |letters|
28
- it "returns false for '#{letters}'" do
29
- expect(node.partial_word? letters).to be false
30
- end
31
- end
32
- end
33
- end
34
- end
35
-
36
- describe '#word?' do
37
- context 'when the chars array is empty' do
38
- context 'when the node is terminal' do
39
- before { node.terminal! }
40
-
41
- it 'returns true' do
42
- expect(node.word? []).to be true
43
- end
44
- end
45
-
46
- context 'when the node is not terminal' do
47
- it 'returns false' do
48
- expect(node.word? []).to be false
49
- end
50
- end
51
- end
52
-
53
- context 'when the chars array is not empty' do
54
- context 'when the node has a tree that matches all the characters' do
55
- before { add_word_to_tree 'abc' }
56
-
57
- it 'returns true' do
58
- expect(node.word? %w(a b c).map(&:dup)).to be true
59
- end
60
- end
61
-
62
- context 'when the node subtree does not match all the characters' do
63
- before { add_word_to_tree 'abc' }
64
-
65
- [%w(a), %w(a b)].each do |letters|
66
- it "returns false for '#{letters}'" do
67
- expect(node.word? letters.map(&:dup)).to be false
68
- end
69
- end
70
- end
71
- end
72
- end
73
-
74
- describe '#scan' do
75
- context 'when the chars array is empty' do
76
- it 'returns itself' do
77
- expect(node.scan []).to eq node
78
- end
79
- end
80
-
81
- context 'when the chars array is not empty' do
82
- before { add_words_to_tree %w(cba ccab) }
83
-
84
- context 'when the chars are found' do
85
- [
86
- [%w(c), %w(cba ccab)],
87
- [%w(c b), %w(cba)],
88
- [%w(c b a), %w(cba)],
89
- ].each do |test_params|
90
- letters, expected = test_params
91
-
92
- it "returns the corresponding children (#{letters} => #{expected})" do
93
- expect(node.scan letters).to match_array expected
94
- end
95
- end
96
- end
97
-
98
- context 'when the chars are not found' do
99
- [
100
- %w(a),
101
- %w(a b),
102
- %w(a b c),
103
- %w(c a),
104
- %w(c c b),
105
- %w(c b a d),
106
- ].each do |letters|
107
- it "returns a Nodes::Missing for '#{letters}'" do
108
- expect(node.scan letters).to be_a Rambling::Trie::Nodes::Missing
109
- end
110
- end
111
- end
112
- end
113
- end
114
-
115
- describe '#match_prefix' do
116
- before do
117
- assign_letter :i
118
- add_words_to_tree %w(gnite mport mportant mportantly)
119
- end
120
-
121
- context 'when the node is terminal' do
122
- before { node.terminal! }
123
-
124
- it 'adds itself to the words' do
125
- expect(node.match_prefix %w(g n i t e)).to include 'i'
126
- end
127
- end
128
-
129
- context 'when the node is not terminal' do
130
- it 'does not add itself to the words' do
131
- expect(node.match_prefix %w(g n i t e)).not_to include 'i'
132
- end
133
- end
134
-
135
- context 'when the first few chars match a terminal node' do
136
- it 'adds those terminal nodes to the words' do
137
- words = node.match_prefix(%w(m p o r t a n t l y)).to_a
138
- expect(words).to include 'import', 'important', 'importantly'
139
- end
140
- end
141
-
142
- context 'when the first few chars do not match a terminal node' do
143
- it 'does not add any other words found' do
144
- words = node.match_prefix(%w(m p m p o r t a n t l y)).to_a
145
- expect(words).not_to include 'import', 'important', 'importantly'
146
- end
147
- end
148
- end
149
- end
data/spec/tmp/.gitkeep DELETED
File without changes