rambling-trie 1.0.2 → 2.2.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 (70) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +6 -3
  3. data/Guardfile +3 -1
  4. data/README.md +30 -12
  5. data/Rakefile +8 -0
  6. data/lib/rambling-trie.rb +2 -0
  7. data/lib/rambling/trie.rb +48 -26
  8. data/lib/rambling/trie/comparable.rb +6 -3
  9. data/lib/rambling/trie/compressible.rb +16 -0
  10. data/lib/rambling/trie/compressor.rb +39 -24
  11. data/lib/rambling/trie/configuration.rb +3 -1
  12. data/lib/rambling/trie/configuration/properties.rb +18 -9
  13. data/lib/rambling/trie/configuration/provider_collection.rb +38 -17
  14. data/lib/rambling/trie/container.rb +123 -36
  15. data/lib/rambling/trie/enumerable.rb +6 -4
  16. data/lib/rambling/trie/inspectable.rb +2 -0
  17. data/lib/rambling/trie/invalid_operation.rb +3 -1
  18. data/lib/rambling/trie/nodes.rb +13 -0
  19. data/lib/rambling/trie/nodes/compressed.rb +98 -0
  20. data/lib/rambling/trie/nodes/missing.rb +12 -0
  21. data/lib/rambling/trie/nodes/node.rb +183 -0
  22. data/lib/rambling/trie/nodes/raw.rb +82 -0
  23. data/lib/rambling/trie/readers.rb +3 -1
  24. data/lib/rambling/trie/readers/plain_text.rb +3 -11
  25. data/lib/rambling/trie/serializers.rb +3 -1
  26. data/lib/rambling/trie/serializers/file.rb +2 -0
  27. data/lib/rambling/trie/serializers/marshal.rb +15 -5
  28. data/lib/rambling/trie/serializers/yaml.rb +21 -5
  29. data/lib/rambling/trie/serializers/zip.rb +15 -8
  30. data/lib/rambling/trie/stringifyable.rb +8 -2
  31. data/lib/rambling/trie/version.rb +3 -1
  32. data/rambling-trie.gemspec +21 -10
  33. data/spec/assets/test_words.es_DO.txt +1 -0
  34. data/spec/integration/rambling/trie_spec.rb +44 -35
  35. data/spec/lib/rambling/trie/comparable_spec.rb +8 -15
  36. data/spec/lib/rambling/trie/compressor_spec.rb +90 -13
  37. data/spec/lib/rambling/trie/configuration/properties_spec.rb +21 -13
  38. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +18 -34
  39. data/spec/lib/rambling/trie/container_spec.rb +183 -217
  40. data/spec/lib/rambling/trie/enumerable_spec.rb +14 -9
  41. data/spec/lib/rambling/trie/inspectable_spec.rb +36 -11
  42. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
  43. data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
  44. data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
  45. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +3 -1
  46. data/spec/lib/rambling/trie/serializers/file_spec.rb +6 -4
  47. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +5 -7
  48. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +5 -7
  49. data/spec/lib/rambling/trie/serializers/zip_spec.rb +18 -20
  50. data/spec/lib/rambling/trie/stringifyable_spec.rb +14 -11
  51. data/spec/lib/rambling/trie_spec.rb +18 -11
  52. data/spec/spec_helper.rb +10 -5
  53. data/spec/support/config.rb +10 -0
  54. data/spec/support/helpers/add_word.rb +20 -0
  55. data/spec/support/helpers/one_line_heredoc.rb +11 -0
  56. data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
  57. data/spec/support/shared_examples/a_serializable_trie.rb +10 -6
  58. data/spec/support/shared_examples/a_serializer.rb +9 -1
  59. data/spec/support/shared_examples/a_trie_data_structure.rb +2 -0
  60. data/spec/support/shared_examples/a_trie_node.rb +127 -0
  61. data/spec/{lib/rambling/trie/compressed_node_spec.rb → support/shared_examples/a_trie_node_implementation.rb} +25 -72
  62. metadata +42 -31
  63. data/lib/rambling/trie/compressable.rb +0 -14
  64. data/lib/rambling/trie/compressed_node.rb +0 -120
  65. data/lib/rambling/trie/missing_node.rb +0 -8
  66. data/lib/rambling/trie/node.rb +0 -97
  67. data/lib/rambling/trie/raw_node.rb +0 -96
  68. data/spec/lib/rambling/trie/node_spec.rb +0 -86
  69. data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
  70. data/spec/support/shared_examples/a_compressable_trie.rb +0 -26
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Stringifyable do
4
6
  describe '#as_word' do
5
- let(:node) { Rambling::Trie::RawNode.new }
7
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
6
8
 
7
9
  context 'for an empty node' do
8
10
  before do
9
- node.add ''
11
+ add_word node, ''
10
12
  end
11
13
 
12
14
  it 'returns nil' do
@@ -17,7 +19,7 @@ describe Rambling::Trie::Stringifyable do
17
19
  context 'for one letter' do
18
20
  before do
19
21
  node.letter = :a
20
- node.add ''
22
+ add_word node, ''
21
23
  end
22
24
 
23
25
  it 'returns the expected one letter word' do
@@ -28,7 +30,7 @@ describe Rambling::Trie::Stringifyable do
28
30
  context 'for a small word' do
29
31
  before do
30
32
  node.letter = :a
31
- node.add 'll'
33
+ add_word node, 'll'
32
34
  end
33
35
 
34
36
  it 'returns the expected small word' do
@@ -36,14 +38,15 @@ describe Rambling::Trie::Stringifyable do
36
38
  end
37
39
 
38
40
  it 'raises an error for a non terminal node' do
39
- expect { node[:l].as_word }.to raise_error Rambling::Trie::InvalidOperation
41
+ expect { node[:l].as_word }
42
+ .to raise_error Rambling::Trie::InvalidOperation
40
43
  end
41
44
  end
42
45
 
43
46
  context 'for a long word' do
44
47
  before do
45
48
  node.letter = :b
46
- node.add 'eautiful'
49
+ add_word node, 'eautiful'
47
50
  end
48
51
 
49
52
  it 'returns the expected long word' do
@@ -52,7 +55,7 @@ describe Rambling::Trie::Stringifyable do
52
55
  end
53
56
 
54
57
  context 'for a node with nil letter' do
55
- let(:node) { Rambling::Trie::RawNode.new nil }
58
+ let(:node) { Rambling::Trie::Nodes::Raw.new nil }
56
59
 
57
60
  it 'returns nil' do
58
61
  expect(node.as_word).to be_empty
@@ -65,17 +68,17 @@ describe Rambling::Trie::Stringifyable do
65
68
 
66
69
  before do
67
70
  node.letter = :a
68
- node.add 'm'
69
- node.add 'dd'
71
+ add_words node, %w(m dd)
70
72
  end
71
73
 
72
74
  it 'returns the words for the terminal nodes' do
73
75
  expect(compressed_node[:m].as_word).to eq 'am'
74
- expect(compressed_node[:dd].as_word).to eq 'add'
76
+ expect(compressed_node[:d].as_word).to eq 'add'
75
77
  end
76
78
 
77
79
  it 'raise an error for non terminal nodes' do
78
- expect { compressed_node.as_word }.to raise_error Rambling::Trie::InvalidOperation
80
+ expect { compressed_node.as_word }
81
+ .to raise_error Rambling::Trie::InvalidOperation
79
82
  end
80
83
  end
81
84
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie do
4
6
  describe '.create' do
5
- let(:root) { Rambling::Trie::RawNode.new }
7
+ let(:root) { Rambling::Trie::Nodes::Raw.new }
6
8
  let(:compressor) { Rambling::Trie::Compressor.new }
7
9
  let!(:container) { Rambling::Trie::Container.new root, compressor }
8
10
 
@@ -31,7 +33,10 @@ describe Rambling::Trie do
31
33
 
32
34
  before do
33
35
  receive_and_yield = receive(:each_word)
34
- words.inject(receive_and_yield) { |yielder, word| yielder.and_yield word }
36
+ words.inject(receive_and_yield) do |yielder, word|
37
+ yielder.and_yield word
38
+ end
39
+
35
40
  allow(reader).to receive_and_yield
36
41
  allow(container).to receive :<<
37
42
  end
@@ -40,7 +45,7 @@ describe Rambling::Trie do
40
45
  Rambling::Trie.create filepath, reader
41
46
 
42
47
  words.each do |word|
43
- expect(container).to have_received(:<<).with(word)
48
+ expect(container).to have_received(:<<).with word
44
49
  end
45
50
  end
46
51
  end
@@ -65,7 +70,7 @@ describe Rambling::Trie do
65
70
  end
66
71
 
67
72
  describe '.load' do
68
- let(:root) { Rambling::Trie::RawNode.new }
73
+ let(:root) { Rambling::Trie::Nodes::Raw.new }
69
74
  let(:compressor) { Rambling::Trie::Compressor.new }
70
75
  let(:container) { Rambling::Trie::Container.new root, compressor }
71
76
  let(:serializer) { double :serializer, load: root }
@@ -77,7 +82,7 @@ describe Rambling::Trie do
77
82
  end
78
83
 
79
84
  it 'uses the serializer to load the root node from the given filepath' do
80
- trie = Rambling::Trie.load filepath, serializer
85
+ Rambling::Trie.load filepath, serializer
81
86
  expect(serializer).to have_received(:load).with filepath
82
87
  end
83
88
 
@@ -98,16 +103,16 @@ describe Rambling::Trie do
98
103
  end
99
104
 
100
105
  it 'determines the serializer based on the file extension' do
101
- trie = Rambling::Trie.load 'test.marshal'
106
+ Rambling::Trie.load 'test.marshal'
102
107
  expect(marshal_serializer).to have_received(:load).with 'test.marshal'
103
108
 
104
- trie = Rambling::Trie.load 'test.yml'
109
+ Rambling::Trie.load 'test.yml'
105
110
  expect(yaml_serializer).to have_received(:load).with 'test.yml'
106
111
 
107
- trie = Rambling::Trie.load 'test.yaml'
112
+ Rambling::Trie.load 'test.yaml'
108
113
  expect(yaml_serializer).to have_received(:load).with 'test.yaml'
109
114
 
110
- trie = Rambling::Trie.load 'test'
115
+ Rambling::Trie.load 'test'
111
116
  expect(default_serializer).to have_received(:load).with 'test'
112
117
  end
113
118
  end
@@ -153,10 +158,12 @@ describe Rambling::Trie do
153
158
  context 'when provided with a format' do
154
159
  it 'uses the corresponding serializer' do
155
160
  Rambling::Trie.dump trie, "#{filename}.marshal"
156
- expect(marshal_serializer).to have_received(:dump).with root, "#{filename}.marshal"
161
+ expect(marshal_serializer).to have_received(:dump)
162
+ .with root, "#{filename}.marshal"
157
163
 
158
164
  Rambling::Trie.dump trie, "#{filename}.yml"
159
- expect(yaml_serializer).to have_received(:dump).with root, "#{filename}.yml"
165
+ expect(yaml_serializer).to have_received(:dump)
166
+ .with root, "#{filename}.yml"
160
167
  end
161
168
  end
162
169
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'simplecov'
2
4
  require 'coveralls'
3
5
 
@@ -5,7 +7,7 @@ Coveralls.wear!
5
7
 
6
8
  SimpleCov.formatters = [
7
9
  SimpleCov::Formatter::HTMLFormatter,
8
- Coveralls::SimpleCov::Formatter
10
+ Coveralls::SimpleCov::Formatter,
9
11
  ]
10
12
 
11
13
  SimpleCov.start do
@@ -26,7 +28,10 @@ RSpec.configure do |config|
26
28
  end
27
29
 
28
30
  require 'support/config'
29
- require 'support/shared_examples/a_compressable_trie'
30
- require 'support/shared_examples/a_serializable_trie'
31
- require 'support/shared_examples/a_serializer'
32
- require 'support/shared_examples/a_trie_data_structure'
31
+
32
+ %w(
33
+ a_compressible_trie a_serializable_trie a_serializer a_trie_data_structure
34
+ a_trie_node a_trie_node_implementation
35
+ ).each do |name|
36
+ require File.join('support', 'shared_examples', name)
37
+ end
@@ -1,5 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers/add_word'
4
+ require_relative 'helpers/one_line_heredoc'
5
+
1
6
  RSpec.configure do |c|
2
7
  c.before do
3
8
  Rambling::Trie.config.reset
4
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
5
15
  end
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for 'a compressible trie' do
4
+ context 'and the trie is not compressed' 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 'and the trie is compressed' 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 intact' do
35
+ expect(original_root.children_tree.keys).to eq original_keys
36
+ expect(trie.children_tree.keys).to eq original_keys
37
+ expect(trie.children_tree.values).not_to eq original_values
38
+ end
39
+ end
40
+ end
@@ -1,20 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
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.#{format}" }
6
+
2
7
  context 'and the trie is not compressed' do
3
8
  before do
4
- Rambling::Trie.dump trie_to_serialize, trie_filepath, serializer
9
+ Rambling::Trie.dump trie_to_serialize, filepath
5
10
  end
6
11
 
7
- it_behaves_like 'a compressable trie' do
8
- let(:trie) { loaded_trie }
12
+ it_behaves_like 'a compressible trie' do
13
+ let(:trie) { Rambling::Trie.load filepath }
9
14
  end
10
15
  end
11
16
 
12
17
  context 'and the trie is compressed' do
13
- let(:trie) { loaded_trie }
18
+ let(:trie) { Rambling::Trie.load filepath }
14
19
 
15
20
  before do
16
- FileUtils.rm_f trie_filepath
17
- Rambling::Trie.dump trie_to_serialize.compress!, trie_filepath, serializer
21
+ Rambling::Trie.dump trie_to_serialize.compress!, filepath
18
22
  end
19
23
 
20
24
  it_behaves_like 'a trie data structure'
@@ -1,5 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  shared_examples_for 'a serializer' do
4
+ let(:trie) { Rambling::Trie.create }
5
+ let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
6
+ let(:filepath) { File.join tmp_path, "trie-root.#{format}" }
7
+ let(:content) { trie.root }
8
+
2
9
  before do
10
+ trie.concat %w(a few words to validate that load and dump are working)
3
11
  FileUtils.rm_f filepath
4
12
  end
5
13
 
@@ -13,7 +21,7 @@ shared_examples_for 'a serializer' do
13
21
  end
14
22
 
15
23
  it 'converts the contents to the appropriate format' do
16
- expect(File.read(filepath).size).to eq formatted_content.size
24
+ expect(File.read(filepath).size).to be_within(10).of formatted_content.size
17
25
  end
18
26
  end
19
27
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  shared_examples_for 'a trie data structure' do
2
4
  it 'contains all the words previously provided' do
3
5
  words.each do |word|
@@ -0,0 +1,127 @@
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
+ let(:node_with_parent) { node_class.new :a, parent }
26
+
27
+ it 'does not have any letter' do
28
+ expect(node_with_parent.letter).to eq :a
29
+ end
30
+
31
+ it 'has no children' do
32
+ expect(node_with_parent.children.size).to eq 0
33
+ end
34
+
35
+ it 'is not terminal' do
36
+ expect(node_with_parent).not_to be_terminal
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#root?' do
42
+ context 'when the node has a parent' do
43
+ before do
44
+ node.parent = node
45
+ end
46
+
47
+ it 'returns false' do
48
+ expect(node).not_to be_root
49
+ end
50
+ end
51
+
52
+ context 'when the node does not have a parent' do
53
+ before do
54
+ node.parent = nil
55
+ end
56
+
57
+ it 'returns true' do
58
+ expect(node).to be_root
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#terminal!' do
64
+ it 'forces the node to be terminal' do
65
+ expect(node).not_to be_terminal
66
+ node.terminal!
67
+
68
+ expect(node).to be_terminal
69
+ end
70
+
71
+ it 'returns the node' do
72
+ expect(node.terminal!).to eq node
73
+ end
74
+ end
75
+
76
+ describe 'delegates and aliases' do
77
+ let(:children_tree) do
78
+ double(
79
+ :children_tree,
80
+ :[] => 'value',
81
+ :[]= => nil,
82
+ key?: false,
83
+ delete: true,
84
+ )
85
+ end
86
+
87
+ before do
88
+ node.children_tree = children_tree
89
+ end
90
+
91
+ it 'delegates `#[]` to its children tree' do
92
+ expect(node[:key]).to eq 'value'
93
+ expect(children_tree).to have_received(:[]).with :key
94
+ end
95
+
96
+ it 'delegates `#[]=` to its children tree' do
97
+ node[:key] = 'value'
98
+ expect(children_tree).to have_received(:[]=).with(:key, 'value')
99
+ end
100
+
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
+
110
+ it 'delegates `#delete` to its children tree' do
111
+ expect(node.delete :key).to be true
112
+ expect(children_tree).to have_received(:delete).with :key
113
+ end
114
+
115
+ it 'delegates `#children` to its children tree values' do
116
+ children = [double(:child_1), double(:child_2)]
117
+ allow(children_tree).to receive(:values).and_return children
118
+
119
+ expect(node.children).to eq children
120
+ end
121
+
122
+ it 'aliases `#has_key?` to `#key?`' do
123
+ node.has_key? :nope
124
+ expect(children_tree).to have_received(:key?).with :nope
125
+ end
126
+ end
127
+ end