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,36 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Rambling
4
6
  module Trie
5
7
  describe Enumerable do
6
- let(:root) { Rambling::Trie::RawNode.new }
8
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
7
9
  let(:words) { %w(add some words and another word) }
8
10
 
9
11
  before do
10
- words.each { |word| root.add word.clone }
12
+ add_words node, words
11
13
  end
12
14
 
13
15
  describe '#each' do
14
16
  it 'returns an enumerator' do
15
- expect(root.each).to be_a Enumerator
17
+ expect(node.each).to be_a Enumerator
16
18
  end
17
19
 
18
20
  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
21
+ node.each do |word|
22
+ expect(words).to include word
23
+ end
24
+
25
+ expect(node.count).to eq words.count
21
26
  end
22
27
  end
23
28
 
24
29
  describe '#size' do
25
30
  it 'delegates to #count' do
26
- expect(root.size).to eq words.size
31
+ expect(node.size).to eq words.size
27
32
  end
28
33
  end
29
34
 
30
35
  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
36
+ expect(node.all? { |word| words.include? word }).to be true
37
+ expect(node.any? { |word| word.start_with? 's' }).to be true
38
+ expect(node.to_a).to match_array words
34
39
  end
35
40
  end
36
41
  end
@@ -1,30 +1,55 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Inspectable do
4
- let(:root) { Rambling::Trie::RawNode.new }
6
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
5
7
 
6
8
  before do
7
- %w(only three words).each { |word| root.add word }
9
+ add_words node, %w(only three words)
8
10
  end
9
11
 
10
12
  describe '#inspect' do
11
- let(:node) { root[:o] }
12
- let(:terminal_node) { root[:o][:n][:l][:y] }
13
+ let(:child) { node[:o] }
14
+ let(:terminal_node) { node[:o][:n][:l][:y] }
13
15
 
14
16
  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: []>"
17
+ expect(node.inspect).to eq one_line <<~RAW
18
+ #<Rambling::Trie::Nodes::Raw letter: nil,
19
+ terminal: nil,
20
+ children: [:o, :t, :w]>
21
+ RAW
22
+
23
+ expect(child.inspect).to eq one_line <<~CHILD
24
+ #<Rambling::Trie::Nodes::Raw letter: :o,
25
+ terminal: nil,
26
+ children: [:n]>
27
+ CHILD
28
+
29
+ expect(terminal_node.inspect).to eq one_line <<~TERMINAL
30
+ #<Rambling::Trie::Nodes::Raw letter: :y,
31
+ terminal: true,
32
+ children: []>
33
+ TERMINAL
18
34
  end
19
35
 
20
36
  context 'for a compressed node' do
21
37
  let(:compressor) { Rambling::Trie::Compressor.new }
22
- let(:compressed_root) { compressor.compress root }
23
- let(:compressed_node) { compressed_root[:only] }
38
+ let(:compressed) { compressor.compress node }
39
+ let(:compressed_child) { compressed[:o] }
24
40
 
25
41
  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: []>"
42
+ expect(compressed.inspect).to eq one_line <<~COMPRESSED
43
+ #<Rambling::Trie::Nodes::Compressed letter: nil,
44
+ terminal: nil,
45
+ children: [:o, :t, :w]>
46
+ COMPRESSED
47
+
48
+ expect(compressed_child.inspect).to eq one_line <<~CHILD
49
+ #<Rambling::Trie::Nodes::Compressed letter: :only,
50
+ terminal: true,
51
+ children: []>
52
+ CHILD
28
53
  end
29
54
  end
30
55
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Nodes::Compressed do
6
+ let(:raw_node) { Rambling::Trie::Nodes::Raw.new }
7
+ let(:compressor) { Rambling::Trie::Compressor.new }
8
+ let(:node) { compressor.compress raw_node }
9
+
10
+ it_behaves_like 'a trie node implementation' do
11
+ def add_word_to_tree word
12
+ add_word raw_node, word
13
+ end
14
+
15
+ def add_words_to_tree words
16
+ add_words raw_node, words
17
+ end
18
+
19
+ def assign_letter letter
20
+ raw_node.letter = letter
21
+ end
22
+ end
23
+
24
+ describe '#compressed?' do
25
+ it 'returns true' do
26
+ expect(node).to be_compressed
27
+ end
28
+ end
29
+
30
+ describe '#add' do
31
+ it 'raises an error' do
32
+ expect do
33
+ add_word node, 'restaurant'
34
+ end.to raise_error Rambling::Trie::InvalidOperation
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Nodes::Node do
6
+ it_behaves_like 'a trie node' do
7
+ let(:node) { Rambling::Trie::Nodes::Node.new }
8
+ end
9
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Nodes::Raw do
6
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
7
+
8
+ it_behaves_like 'a trie node implementation' do
9
+ def add_word_to_tree word
10
+ add_word node, word
11
+ end
12
+
13
+ def add_words_to_tree words
14
+ add_words node, words
15
+ end
16
+
17
+ def assign_letter letter
18
+ node.letter = letter
19
+ end
20
+
21
+ it 'is not a word' do
22
+ expect(node).not_to be_word
23
+ end
24
+ end
25
+
26
+ describe '#compressed?' do
27
+ it 'returns false' do
28
+ expect(node).not_to be_compressed
29
+ end
30
+ end
31
+
32
+ describe '#add' do
33
+ context 'when the node has no branches' do
34
+ before do
35
+ add_word node, 'abc'
36
+ end
37
+
38
+ it 'adds only one child' do
39
+ expect(node.children.size).to eq 1
40
+ end
41
+
42
+ it 'adds the full subtree' do
43
+ expect(node[:a]).not_to be_nil
44
+ expect(node[:a][:b]).not_to be_nil
45
+ expect(node[:a][:b][:c]).not_to be_nil
46
+ end
47
+
48
+ it 'marks only the last child as terminal' do
49
+ expect(node).not_to be_terminal
50
+ expect(node[:a]).not_to be_terminal
51
+ expect(node[:a][:b]).not_to be_terminal
52
+ expect(node[:a][:b][:c]).to be_terminal
53
+ end
54
+ end
55
+
56
+ context 'when a word is added more than once' do
57
+ before do
58
+ add_word node, 'ack'
59
+ add_word node, 'ack'
60
+ end
61
+
62
+ it 'only counts it once' do
63
+ expect(node.children.size).to eq 1
64
+ expect(node[:a].children.size).to eq 1
65
+ expect(node[:a][:c].children.size).to eq 1
66
+ expect(node[:a][:c][:k].children.size).to eq 0
67
+ end
68
+
69
+ it 'does not change the terminal nodes in the tree' do
70
+ expect(node).not_to be_terminal
71
+ expect(node[:a]).not_to be_terminal
72
+ expect(node[:a][:c]).not_to be_terminal
73
+ expect(node[:a][:c][:k]).to be_terminal
74
+ end
75
+
76
+ it 'still returns the "added" node' do
77
+ child = add_word node, 'ack'
78
+ expect(child.letter).to eq :a
79
+ end
80
+ end
81
+
82
+ context 'when the word does not exist in the tree but the letters do' do
83
+ before do
84
+ add_words node, %w(ack a)
85
+ end
86
+
87
+ it 'does not add another branch' do
88
+ expect(node.children.size).to eq 1
89
+ end
90
+
91
+ it 'marks the corresponding node as terminal' do
92
+ expect(node[:a]).to be_terminal
93
+
94
+ expect(node).not_to be_terminal
95
+ expect(node[:a][:c]).not_to be_terminal
96
+ expect(node[:a][:c][:k]).to be_terminal
97
+ end
98
+
99
+ it 'returns the added node' do
100
+ child = add_word node, 'a'
101
+ expect(child.letter).to eq :a
102
+ end
103
+ end
104
+
105
+ context 'when the node has a letter and a parent' do
106
+ let(:parent) { Rambling::Trie::Nodes::Raw.new }
107
+ let(:node) { Rambling::Trie::Nodes::Raw.new :a, parent }
108
+
109
+ context 'adding an empty string' do
110
+ before do
111
+ add_word node, ''
112
+ end
113
+
114
+ it 'does not alter the node letter' do
115
+ expect(node.letter).to eq :a
116
+ end
117
+
118
+ it 'does not change the node children' do
119
+ expect(node.children.size).to eq 0
120
+ end
121
+
122
+ it 'changes the node to terminal' do
123
+ expect(node).to be_terminal
124
+ end
125
+ end
126
+
127
+ context 'adding a one letter word' do
128
+ before do
129
+ add_word node, 'b'
130
+ end
131
+
132
+ it 'does not alter the node letter' do
133
+ expect(node.letter).to eq :a
134
+ end
135
+
136
+ it 'adds a child with the expected letter' do
137
+ expect(node.children.size).to eq 1
138
+ expect(node.children.first.letter).to eq :b
139
+ end
140
+
141
+ it 'reports it has the expected letter a key' do
142
+ expect(node).to have_key(:b)
143
+ end
144
+
145
+ it 'returns the child corresponding to the key' do
146
+ expect(node[:b]).to eq node.children_tree[:b]
147
+ end
148
+
149
+ it 'does not mark itself as terminal' do
150
+ expect(node).not_to be_terminal
151
+ end
152
+
153
+ it 'marks the first child as terminal' do
154
+ expect(node[:b]).to be_terminal
155
+ end
156
+ end
157
+
158
+ context 'adding a large word' do
159
+ before do
160
+ add_word node, 'mplified'
161
+ end
162
+
163
+ it 'marks the last letter as terminal' do
164
+ expect(node[:m][:p][:l][:i][:f][:i][:e][:d]).to be_terminal
165
+ end
166
+
167
+ it 'does not mark any other letter as terminal' do
168
+ expect(node[:m][:p][:l][:i][:f][:i][:e]).not_to be_terminal
169
+ expect(node[:m][:p][:l][:i][:f][:i]).not_to be_terminal
170
+ expect(node[:m][:p][:l][:i][:f]).not_to be_terminal
171
+ expect(node[:m][:p][:l][:i]).not_to be_terminal
172
+ expect(node[:m][:p][:l]).not_to be_terminal
173
+ expect(node[:m][:p]).not_to be_terminal
174
+ expect(node[:m]).not_to be_terminal
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Readers::PlainText do
4
6
  describe '#each_word' do
5
7
  let(:filepath) { File.join(::SPEC_ROOT, 'assets', 'test_words.en_US.txt') }
6
- let(:words) { File.readlines(filepath).map &:chomp }
8
+ let(:words) { File.readlines(filepath).map(&:chomp) }
7
9
 
8
10
  it 'yields every word yielded by the file' do
9
11
  yielded = []
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Serializers::File do
4
- let(:serializer) { Rambling::Trie::Serializers::File.new }
5
-
6
6
  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' }
7
+ let(:serializer) { Rambling::Trie::Serializers::File.new }
8
+ let(:format) { :file }
9
+
10
+ let(:content) { trie.to_a.join ' ' }
9
11
  let(:formatted_content) { content }
10
12
  end
11
13
  end
@@ -1,14 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  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
6
  it_behaves_like 'a serializer' do
10
- let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.marshal' }
11
- let(:content) { trie.root }
7
+ let(:serializer) { Rambling::Trie::Serializers::Marshal.new }
8
+ let(:format) { :marshal }
9
+
12
10
  let(:formatted_content) { Marshal.dump content }
13
11
  end
14
12
  end
@@ -1,14 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  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
6
  it_behaves_like 'a serializer' do
10
- let(:filepath) { File.join ::SPEC_ROOT, 'tmp', 'trie-root.yml' }
11
- let(:content) { trie.root }
7
+ let(:serializer) { Rambling::Trie::Serializers::Yaml.new }
8
+ let(:format) { :yml }
9
+
12
10
  let(:formatted_content) { YAML.dump content }
13
11
  end
14
12
  end
@@ -1,30 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  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' }
6
+ it_behaves_like 'a serializer' do
7
+ let(:properties) { Rambling::Trie::Configuration::Properties.new }
8
+ let(:serializer) { Rambling::Trie::Serializers::Zip.new properties }
9
+ let(:format) { 'marshal.zip' }
10
10
 
11
- before do
12
- properties.tmp_path = tmp_path
13
- end
11
+ before do
12
+ properties.tmp_path = tmp_path
13
+ end
14
14
 
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 }
15
+ let(:filename) { File.basename(filepath).gsub %r{\.zip}, '' }
19
16
  let(:formatted_content) { zip Marshal.dump content }
20
- end
21
17
 
22
- def zip content
23
- io = Zip::OutputStream.write_buffer do |io|
24
- io.put_next_entry filename
25
- io.write content
18
+ def zip content
19
+ cursor = Zip::OutputStream.write_buffer do |io|
20
+ io.put_next_entry filename
21
+ io.write content
22
+ end
23
+
24
+ cursor.rewind
25
+ cursor.read
26
26
  end
27
- io.rewind
28
- io.read
29
27
  end
30
28
  end