rambling-trie 1.0.1 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) 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 +40 -33
  63. data/lib/rambling/trie/compressable.rb +0 -14
  64. data/lib/rambling/trie/compressed_node.rb +0 -120
  65. data/lib/rambling/trie/forwardable.rb +0 -21
  66. data/lib/rambling/trie/missing_node.rb +0 -8
  67. data/lib/rambling/trie/node.rb +0 -97
  68. data/lib/rambling/trie/raw_node.rb +0 -96
  69. data/spec/lib/rambling/trie/node_spec.rb +0 -86
  70. data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
  71. 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