rambling-trie 1.0.1 → 2.1.1

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 (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