rambling-trie 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/README.md +23 -7
  4. data/Rakefile +4 -0
  5. data/lib/rambling/trie.rb +27 -21
  6. data/lib/rambling/trie/comparable.rb +3 -3
  7. data/lib/rambling/trie/compressible.rb +14 -0
  8. data/lib/rambling/trie/compressor.rb +37 -24
  9. data/lib/rambling/trie/configuration/properties.rb +8 -6
  10. data/lib/rambling/trie/configuration/provider_collection.rb +34 -16
  11. data/lib/rambling/trie/container.rb +156 -36
  12. data/lib/rambling/trie/enumerable.rb +4 -4
  13. data/lib/rambling/trie/nodes.rb +11 -0
  14. data/lib/rambling/trie/nodes/compressed.rb +115 -0
  15. data/lib/rambling/trie/nodes/missing.rb +10 -0
  16. data/lib/rambling/trie/nodes/node.rb +151 -0
  17. data/lib/rambling/trie/nodes/raw.rb +89 -0
  18. data/lib/rambling/trie/readers/plain_text.rb +1 -11
  19. data/lib/rambling/trie/serializers/marshal.rb +4 -4
  20. data/lib/rambling/trie/serializers/yaml.rb +4 -4
  21. data/lib/rambling/trie/serializers/zip.rb +9 -8
  22. data/lib/rambling/trie/version.rb +1 -1
  23. data/spec/assets/test_words.es_DO.txt +1 -0
  24. data/spec/integration/rambling/trie_spec.rb +40 -35
  25. data/spec/lib/rambling/trie/comparable_spec.rb +6 -15
  26. data/spec/lib/rambling/trie/compressor_spec.rb +88 -13
  27. data/spec/lib/rambling/trie/configuration/properties_spec.rb +7 -7
  28. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +8 -20
  29. data/spec/lib/rambling/trie/container_spec.rb +159 -168
  30. data/spec/lib/rambling/trie/enumerable_spec.rb +12 -9
  31. data/spec/lib/rambling/trie/inspectable_spec.rb +11 -11
  32. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +35 -0
  33. data/spec/lib/rambling/trie/nodes/node_spec.rb +7 -0
  34. data/spec/lib/rambling/trie/nodes/raw_spec.rb +177 -0
  35. data/spec/lib/rambling/trie/serializers/file_spec.rb +4 -4
  36. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +3 -7
  37. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +3 -7
  38. data/spec/lib/rambling/trie/serializers/zip_spec.rb +16 -20
  39. data/spec/lib/rambling/trie/stringifyable_spec.rb +7 -8
  40. data/spec/lib/rambling/trie_spec.rb +2 -2
  41. data/spec/spec_helper.rb +3 -1
  42. data/spec/support/config.rb +4 -0
  43. data/spec/support/helpers/add_word.rb +18 -0
  44. data/spec/support/shared_examples/{a_compressable_trie.rb → a_compressible_trie.rb} +13 -3
  45. data/spec/support/shared_examples/a_serializable_trie.rb +8 -6
  46. data/spec/support/shared_examples/a_serializer.rb +6 -0
  47. data/spec/{lib/rambling/trie/node_spec.rb → support/shared_examples/a_trie_node.rb} +61 -30
  48. data/spec/{lib/rambling/trie/compressed_node_spec.rb → support/shared_examples/a_trie_node_implementation.rb} +18 -69
  49. metadata +22 -15
  50. data/lib/rambling/trie/compressable.rb +0 -14
  51. data/lib/rambling/trie/compressed_node.rb +0 -120
  52. data/lib/rambling/trie/missing_node.rb +0 -8
  53. data/lib/rambling/trie/node.rb +0 -97
  54. data/lib/rambling/trie/raw_node.rb +0 -96
  55. data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
@@ -3,34 +3,37 @@ require 'spec_helper'
3
3
  module Rambling
4
4
  module Trie
5
5
  describe Enumerable do
6
- let(:root) { Rambling::Trie::RawNode.new }
6
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
7
7
  let(:words) { %w(add some words and another word) }
8
8
 
9
9
  before do
10
- words.each { |word| root.add word.clone }
10
+ add_words node, words
11
11
  end
12
12
 
13
13
  describe '#each' do
14
14
  it 'returns an enumerator' do
15
- expect(root.each).to be_a Enumerator
15
+ expect(node.each).to be_a Enumerator
16
16
  end
17
17
 
18
18
  it 'includes every word contained in the trie' do
19
- root.each { |word| expect(words).to include word }
20
- expect(root.count).to eq words.count
19
+ node.each do |word|
20
+ expect(words).to include word
21
+ end
22
+
23
+ expect(node.count).to eq words.count
21
24
  end
22
25
  end
23
26
 
24
27
  describe '#size' do
25
28
  it 'delegates to #count' do
26
- expect(root.size).to eq words.size
29
+ expect(node.size).to eq words.size
27
30
  end
28
31
  end
29
32
 
30
33
  it 'includes the core Enumerable module' do
31
- expect(root.all? { |word| words.include? word }).to be true
32
- expect(root.any? { |word| word.start_with? 's' }).to be true
33
- expect(root.to_a).to match_array words
34
+ expect(node.all? { |word| words.include? word }).to be true
35
+ expect(node.any? { |word| word.start_with? 's' }).to be true
36
+ expect(node.to_a).to match_array words
34
37
  end
35
38
  end
36
39
  end
@@ -1,30 +1,30 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Rambling::Trie::Inspectable do
4
- let(:root) { Rambling::Trie::RawNode.new }
4
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
5
5
 
6
6
  before do
7
- %w(only three words).each { |word| root.add word }
7
+ add_words node, %w(only three words)
8
8
  end
9
9
 
10
10
  describe '#inspect' do
11
- let(:node) { root[:o] }
12
- let(:terminal_node) { root[:o][:n][:l][:y] }
11
+ let(:child) { node[:o] }
12
+ let(:terminal_node) { node[:o][:n][:l][:y] }
13
13
 
14
14
  it 'returns a pretty printed version of the node' do
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: []>"
15
+ expect(node.inspect).to eq "#<Rambling::Trie::Nodes::Raw letter: nil, terminal: nil, children: [:o, :t, :w]>"
16
+ expect(child.inspect).to eq "#<Rambling::Trie::Nodes::Raw letter: :o, terminal: nil, children: [:n]>"
17
+ expect(terminal_node.inspect).to eq "#<Rambling::Trie::Nodes::Raw letter: :y, terminal: true, children: []>"
18
18
  end
19
19
 
20
20
  context 'for a compressed node' do
21
21
  let(:compressor) { Rambling::Trie::Compressor.new }
22
- let(:compressed_root) { compressor.compress root }
23
- let(:compressed_node) { compressed_root[:only] }
22
+ let(:compressed_node) { compressor.compress node }
23
+ let(:compressed_child) { compressed_node[:only] }
24
24
 
25
25
  it 'returns a pretty printed version of the compressed node' do
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
+ expect(compressed_node.inspect).to eq "#<Rambling::Trie::Nodes::Compressed letter: nil, terminal: nil, children: [:only, :three, :words]>"
27
+ expect(compressed_child.inspect).to eq "#<Rambling::Trie::Nodes::Compressed letter: :only, terminal: true, children: []>"
28
28
  end
29
29
  end
30
30
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Nodes::Compressed do
4
+ let(:raw_node) { Rambling::Trie::Nodes::Raw.new }
5
+ let(:compressor) { Rambling::Trie::Compressor.new }
6
+ let(:node) { compressor.compress raw_node }
7
+
8
+ it_behaves_like 'a trie node implementation' do
9
+ def add_word_to_tree word
10
+ add_word raw_node, word
11
+ end
12
+
13
+ def add_words_to_tree words
14
+ add_words raw_node, words
15
+ end
16
+
17
+ def assign_letter letter
18
+ raw_node.letter = letter
19
+ end
20
+ end
21
+
22
+ describe '#compressed?' do
23
+ it 'returns true' do
24
+ expect(node).to be_compressed
25
+ end
26
+ end
27
+
28
+ describe '#add' do
29
+ it 'raises an error' do
30
+ expect do
31
+ add_word node, 'restaurant'
32
+ end.to raise_error Rambling::Trie::InvalidOperation
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Nodes::Node do
4
+ it_behaves_like 'a trie node' do
5
+ let(:node) { Rambling::Trie::Nodes::Node.new }
6
+ end
7
+ end
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Nodes::Raw do
4
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
5
+
6
+ it_behaves_like 'a trie node implementation' do
7
+ def add_word_to_tree word
8
+ add_word node, word
9
+ end
10
+
11
+ def add_words_to_tree words
12
+ add_words node, words
13
+ end
14
+
15
+ def assign_letter letter
16
+ node.letter = letter
17
+ end
18
+
19
+ it 'is not a word' do
20
+ expect(node).not_to be_word
21
+ end
22
+ end
23
+
24
+ describe '#compressed?' do
25
+ it 'returns false' do
26
+ expect(node).not_to be_compressed
27
+ end
28
+ end
29
+
30
+ describe '#add' do
31
+ context 'when the node has no branches' do
32
+ before do
33
+ add_word node, 'abc'
34
+ end
35
+
36
+ it 'adds only one child' do
37
+ expect(node.children.size).to eq 1
38
+ end
39
+
40
+ it 'adds the full subtree' do
41
+ expect(node[:a]).not_to be_nil
42
+ expect(node[:a][:b]).not_to be_nil
43
+ expect(node[:a][:b][:c]).not_to be_nil
44
+ end
45
+
46
+ it 'marks only the last child as terminal' do
47
+ expect(node).not_to be_terminal
48
+ expect(node[:a]).not_to be_terminal
49
+ expect(node[:a][:b]).not_to be_terminal
50
+ expect(node[:a][:b][:c]).to be_terminal
51
+ end
52
+ end
53
+
54
+ context 'when a word is added more than once' do
55
+ before do
56
+ add_word node, 'ack'
57
+ add_word node, 'ack'
58
+ end
59
+
60
+ it 'only counts it once' do
61
+ expect(node.children.size).to eq 1
62
+ expect(node[:a].children.size).to eq 1
63
+ expect(node[:a][:c].children.size).to eq 1
64
+ expect(node[:a][:c][:k].children.size).to eq 0
65
+ end
66
+
67
+ it 'does not change the terminal nodes in the tree' do
68
+ expect(node).not_to be_terminal
69
+ expect(node[:a]).not_to be_terminal
70
+ expect(node[:a][:c]).not_to be_terminal
71
+ expect(node[:a][:c][:k]).to be_terminal
72
+ end
73
+
74
+ it 'still returns the "added" node' do
75
+ child = add_word node, 'ack'
76
+ expect(child.letter).to eq :a
77
+ end
78
+ end
79
+
80
+ context 'when the word does not exist in the tree but the letters do' do
81
+ before do
82
+ add_words node, %w(ack a)
83
+ end
84
+
85
+ it 'does not add another branch' do
86
+ expect(node.children.size).to eq 1
87
+ end
88
+
89
+ it 'marks the corresponding node as terminal' do
90
+ expect(node[:a]).to be_terminal
91
+
92
+ expect(node).not_to be_terminal
93
+ expect(node[:a][:c]).not_to be_terminal
94
+ expect(node[:a][:c][:k]).to be_terminal
95
+ end
96
+
97
+ it 'returns the added node' do
98
+ child = add_word node, 'a'
99
+ expect(child.letter).to eq :a
100
+ end
101
+ end
102
+
103
+ context 'when the node has a letter and a parent' do
104
+ let(:parent) { Rambling::Trie::Nodes::Raw.new }
105
+ let(:node) { Rambling::Trie::Nodes::Raw.new :a, parent }
106
+
107
+ context 'adding an empty string' do
108
+ before do
109
+ add_word node, ''
110
+ end
111
+
112
+ it 'does not alter the node letter' do
113
+ expect(node.letter).to eq :a
114
+ end
115
+
116
+ it 'does not change the node children' do
117
+ expect(node.children.size).to eq 0
118
+ end
119
+
120
+ it 'changes the node to terminal' do
121
+ expect(node).to be_terminal
122
+ end
123
+ end
124
+
125
+ context 'adding a one letter word' do
126
+ before do
127
+ add_word node, 'b'
128
+ end
129
+
130
+ it 'does not alter the node letter' do
131
+ expect(node.letter).to eq :a
132
+ end
133
+
134
+ it 'adds a child with the expected letter' do
135
+ expect(node.children.size).to eq 1
136
+ expect(node.children.first.letter).to eq :b
137
+ end
138
+
139
+ it 'reports it has the expected letter a key' do
140
+ expect(node).to have_key(:b)
141
+ end
142
+
143
+ it 'returns the child corresponding to the key' do
144
+ expect(node[:b]).to eq node.children_tree[:b]
145
+ end
146
+
147
+ it 'does not mark itself as terminal' do
148
+ expect(node).not_to be_terminal
149
+ end
150
+
151
+ it 'marks the first child as terminal' do
152
+ expect(node[:b]).to be_terminal
153
+ end
154
+ end
155
+
156
+ context 'adding a large word' do
157
+ before do
158
+ add_word node, 'mplified'
159
+ end
160
+
161
+ it 'marks the last letter as terminal' do
162
+ expect(node[:m][:p][:l][:i][:f][:i][:e][:d]).to be_terminal
163
+ end
164
+
165
+ it 'does not mark any other letter as terminal' do
166
+ expect(node[:m][:p][:l][:i][:f][:i][:e]).not_to be_terminal
167
+ expect(node[:m][:p][:l][:i][:f][:i]).not_to be_terminal
168
+ expect(node[:m][:p][:l][:i][:f]).not_to be_terminal
169
+ expect(node[:m][:p][:l][:i]).not_to be_terminal
170
+ expect(node[:m][:p][:l]).not_to be_terminal
171
+ expect(node[:m][:p]).not_to be_terminal
172
+ expect(node[:m]).not_to be_terminal
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Rambling::Trie::Serializers::File do
4
- let(:serializer) { Rambling::Trie::Serializers::File.new }
5
-
6
4
  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' }
5
+ let(:serializer) { Rambling::Trie::Serializers::File.new }
6
+ let(:format) { :file }
7
+
8
+ let(:content) { trie.to_a.join ' ' }
9
9
  let(:formatted_content) { content }
10
10
  end
11
11
  end
@@ -1,14 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
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
4
  it_behaves_like 'a serializer' do
10
- let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.marshal' }
11
- let(:content) { trie.root }
5
+ let(:serializer) { Rambling::Trie::Serializers::Marshal.new }
6
+ let(:format) { :marshal }
7
+
12
8
  let(:formatted_content) { Marshal.dump content }
13
9
  end
14
10
  end
@@ -1,14 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
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
4
  it_behaves_like 'a serializer' do
10
- let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.yml' }
11
- let(:content) { trie.root }
5
+ let(:serializer) { Rambling::Trie::Serializers::Yaml.new }
6
+ let(:format) { :yml }
7
+
12
8
  let(:formatted_content) { YAML.dump content }
13
9
  end
14
10
  end
@@ -1,30 +1,26 @@
1
1
  require 'spec_helper'
2
2
 
3
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' }
4
+ it_behaves_like 'a serializer' do
5
+ let(:properties) { Rambling::Trie::Configuration::Properties.new }
6
+ let(:serializer) { Rambling::Trie::Serializers::Zip.new properties }
7
+ let(:format) { 'marshal.zip' }
10
8
 
11
- before do
12
- properties.tmp_path = tmp_path
13
- end
9
+ before do
10
+ properties.tmp_path = tmp_path
11
+ end
14
12
 
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 }
13
+ let(:filename) { File.basename(filepath).gsub /\.zip/, ''}
19
14
  let(:formatted_content) { zip Marshal.dump content }
20
- end
21
15
 
22
- def zip content
23
- io = Zip::OutputStream.write_buffer do |io|
24
- io.put_next_entry filename
25
- io.write content
16
+ def zip content
17
+ io = Zip::OutputStream.write_buffer do |io|
18
+ io.put_next_entry filename
19
+ io.write content
20
+ end
21
+
22
+ io.rewind
23
+ io.read
26
24
  end
27
- io.rewind
28
- io.read
29
25
  end
30
26
  end
@@ -2,11 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Rambling::Trie::Stringifyable do
4
4
  describe '#as_word' do
5
- let(:node) { Rambling::Trie::RawNode.new }
5
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
6
6
 
7
7
  context 'for an empty node' do
8
8
  before do
9
- node.add ''
9
+ add_word node, ''
10
10
  end
11
11
 
12
12
  it 'returns nil' do
@@ -17,7 +17,7 @@ describe Rambling::Trie::Stringifyable do
17
17
  context 'for one letter' do
18
18
  before do
19
19
  node.letter = :a
20
- node.add ''
20
+ add_word node, ''
21
21
  end
22
22
 
23
23
  it 'returns the expected one letter word' do
@@ -28,7 +28,7 @@ describe Rambling::Trie::Stringifyable do
28
28
  context 'for a small word' do
29
29
  before do
30
30
  node.letter = :a
31
- node.add 'll'
31
+ add_word node, 'll'
32
32
  end
33
33
 
34
34
  it 'returns the expected small word' do
@@ -43,7 +43,7 @@ describe Rambling::Trie::Stringifyable do
43
43
  context 'for a long word' do
44
44
  before do
45
45
  node.letter = :b
46
- node.add 'eautiful'
46
+ add_word node, 'eautiful'
47
47
  end
48
48
 
49
49
  it 'returns the expected long word' do
@@ -52,7 +52,7 @@ describe Rambling::Trie::Stringifyable do
52
52
  end
53
53
 
54
54
  context 'for a node with nil letter' do
55
- let(:node) { Rambling::Trie::RawNode.new nil }
55
+ let(:node) { Rambling::Trie::Nodes::Raw.new nil }
56
56
 
57
57
  it 'returns nil' do
58
58
  expect(node.as_word).to be_empty
@@ -65,8 +65,7 @@ describe Rambling::Trie::Stringifyable do
65
65
 
66
66
  before do
67
67
  node.letter = :a
68
- node.add 'm'
69
- node.add 'dd'
68
+ add_words node, %w(m dd)
70
69
  end
71
70
 
72
71
  it 'returns the words for the terminal nodes' do