rambling-trie 2.4.0 → 2.5.0

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