rambling-trie 2.2.1 → 2.3.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/LICENSE +1 -1
  4. data/README.md +37 -21
  5. data/lib/rambling/trie/comparable.rb +2 -2
  6. data/lib/rambling/trie/compressible.rb +3 -3
  7. data/lib/rambling/trie/configuration/properties.rb +10 -9
  8. data/lib/rambling/trie/configuration/provider_collection.rb +17 -15
  9. data/lib/rambling/trie/container.rb +18 -20
  10. data/lib/rambling/trie/enumerable.rb +4 -1
  11. data/lib/rambling/trie/nodes/compressed.rb +3 -3
  12. data/lib/rambling/trie/nodes/node.rb +18 -18
  13. data/lib/rambling/trie/nodes/raw.rb +2 -2
  14. data/lib/rambling/trie/readers/plain_text.rb +9 -4
  15. data/lib/rambling/trie/readers/reader.rb +21 -0
  16. data/lib/rambling/trie/readers.rb +1 -1
  17. data/lib/rambling/trie/serializers/file.rb +1 -1
  18. data/lib/rambling/trie/serializers/marshal.rb +6 -5
  19. data/lib/rambling/trie/serializers/serializer.rb +27 -0
  20. data/lib/rambling/trie/serializers/yaml.rb +7 -7
  21. data/lib/rambling/trie/serializers/zip.rb +9 -5
  22. data/lib/rambling/trie/serializers.rb +1 -1
  23. data/lib/rambling/trie/stringifyable.rb +1 -1
  24. data/lib/rambling/trie/version.rb +1 -1
  25. data/lib/rambling/trie.rb +12 -10
  26. data/rambling-trie.gemspec +8 -4
  27. data/spec/integration/rambling/trie_spec.rb +13 -16
  28. data/spec/lib/rambling/trie/comparable_spec.rb +29 -39
  29. data/spec/lib/rambling/trie/compressor_spec.rb +17 -14
  30. data/spec/lib/rambling/trie/configuration/properties_spec.rb +25 -7
  31. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +42 -14
  32. data/spec/lib/rambling/trie/container_spec.rb +200 -325
  33. data/spec/lib/rambling/trie/enumerable_spec.rb +18 -10
  34. data/spec/lib/rambling/trie/inspectable_spec.rb +9 -3
  35. data/spec/lib/rambling/trie/nodes/node_spec.rb +1 -1
  36. data/spec/lib/rambling/trie/nodes/raw_spec.rb +26 -23
  37. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +11 -1
  38. data/spec/lib/rambling/trie/readers/reader_spec.rb +14 -0
  39. data/spec/lib/rambling/trie/serializers/file_spec.rb +1 -3
  40. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +1 -3
  41. data/spec/lib/rambling/trie/serializers/serializer_spec.rb +21 -0
  42. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +1 -3
  43. data/spec/lib/rambling/trie/serializers/zip_spec.rb +24 -16
  44. data/spec/lib/rambling/trie/stringifyable_spec.rb +17 -13
  45. data/spec/lib/rambling/trie_spec.rb +106 -44
  46. data/spec/spec_helper.rb +14 -9
  47. data/spec/support/shared_examples/a_compressible_trie.rb +9 -3
  48. data/spec/support/shared_examples/a_container_partial_word.rb +17 -0
  49. data/spec/support/shared_examples/a_container_scan.rb +14 -0
  50. data/spec/support/shared_examples/a_container_word.rb +43 -0
  51. data/spec/support/shared_examples/a_container_words_within.rb +44 -0
  52. data/spec/support/shared_examples/a_serializable_trie.rb +4 -8
  53. data/spec/support/shared_examples/a_serializer.rb +36 -13
  54. data/spec/support/shared_examples/a_trie_data_structure.rb +24 -10
  55. data/spec/support/shared_examples/a_trie_node.rb +19 -12
  56. data/spec/support/shared_examples/a_trie_node_implementation.rb +40 -43
  57. metadata +26 -10
@@ -8,21 +8,23 @@ module Rambling
8
8
  let(:node) { Rambling::Trie::Nodes::Raw.new }
9
9
  let(:words) { %w(add some words and another word) }
10
10
 
11
- before do
12
- add_words node, words
13
- end
11
+ before { add_words node, words }
14
12
 
15
13
  describe '#each' do
16
- it 'returns an enumerator' do
17
- expect(node.each).to be_a Enumerator
14
+ it 'returns an enumerator when no block is given' do
15
+ expect(node.each).to be_an Enumerator
16
+ end
17
+
18
+ it 'has the same word count as the trie' do
19
+ expect(node.count).to eq words.count
18
20
  end
19
21
 
20
22
  it 'includes every word contained in the trie' do
21
- node.each do |word|
22
- expect(words).to include word
23
- end
23
+ node.each { |word| expect(words).to include word }
24
+ end
24
25
 
25
- expect(node.count).to eq words.count
26
+ it 'returns the enumerable when a block is given' do
27
+ expect(node.each { |_| }).to eq node
26
28
  end
27
29
  end
28
30
 
@@ -32,9 +34,15 @@ module Rambling
32
34
  end
33
35
  end
34
36
 
35
- it 'includes the core Enumerable module' do
37
+ it 'includes #all? from the core Enumerable module' do
36
38
  expect(node.all? { |word| words.include? word }).to be true
39
+ end
40
+
41
+ it 'includes #any? from the core Enumerable module' do
37
42
  expect(node.any? { |word| word.start_with? 's' }).to be true
43
+ end
44
+
45
+ it 'includes #to_a from the core Enumerable module' do
38
46
  expect(node.to_a).to match_array words
39
47
  end
40
48
  end
@@ -13,19 +13,23 @@ describe Rambling::Trie::Inspectable do
13
13
  let(:child) { node[:o] }
14
14
  let(:terminal_node) { node[:o][:n][:l][:y] }
15
15
 
16
- it 'returns a pretty printed version of the node' do
16
+ it 'returns a pretty printed version of the parent node' do
17
17
  expect(node.inspect).to eq one_line <<~RAW
18
18
  #<Rambling::Trie::Nodes::Raw letter: nil,
19
19
  terminal: nil,
20
20
  children: [:o, :t, :w]>
21
21
  RAW
22
+ end
22
23
 
24
+ it 'returns a pretty printed version of the child node' do
23
25
  expect(child.inspect).to eq one_line <<~CHILD
24
26
  #<Rambling::Trie::Nodes::Raw letter: :o,
25
27
  terminal: nil,
26
28
  children: [:n]>
27
29
  CHILD
30
+ end
28
31
 
32
+ it 'returns a pretty printed version of the terminal node' do
29
33
  expect(terminal_node.inspect).to eq one_line <<~TERMINAL
30
34
  #<Rambling::Trie::Nodes::Raw letter: :y,
31
35
  terminal: true,
@@ -33,18 +37,20 @@ describe Rambling::Trie::Inspectable do
33
37
  TERMINAL
34
38
  end
35
39
 
36
- context 'for a compressed node' do
40
+ context 'with a compressed node' do
37
41
  let(:compressor) { Rambling::Trie::Compressor.new }
38
42
  let(:compressed) { compressor.compress node }
39
43
  let(:compressed_child) { compressed[:o] }
40
44
 
41
- it 'returns a pretty printed version of the compressed node' do
45
+ it 'returns a pretty printed version of the compressed parent node' do
42
46
  expect(compressed.inspect).to eq one_line <<~COMPRESSED
43
47
  #<Rambling::Trie::Nodes::Compressed letter: nil,
44
48
  terminal: nil,
45
49
  children: [:o, :t, :w]>
46
50
  COMPRESSED
51
+ end
47
52
 
53
+ it 'returns a pretty printed version of the compressed child node' do
48
54
  expect(compressed_child.inspect).to eq one_line <<~CHILD
49
55
  #<Rambling::Trie::Nodes::Compressed letter: :only,
50
56
  terminal: true,
@@ -4,6 +4,6 @@ require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Nodes::Node do
6
6
  it_behaves_like 'a trie node' do
7
- let(:node) { Rambling::Trie::Nodes::Node.new }
7
+ let(:node) { described_class.new }
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Nodes::Raw do
6
- let(:node) { Rambling::Trie::Nodes::Raw.new }
6
+ let(:node) { described_class.new }
7
7
 
8
8
  it_behaves_like 'a trie node implementation' do
9
9
  def add_word_to_tree word
@@ -31,26 +31,28 @@ describe Rambling::Trie::Nodes::Raw do
31
31
 
32
32
  describe '#add' do
33
33
  context 'when the node has no branches' do
34
- before do
35
- add_word node, 'abc'
36
- end
34
+ before { add_word node, 'abc' }
37
35
 
38
36
  it 'adds only one child' do
39
37
  expect(node.children.size).to eq 1
40
38
  end
41
39
 
40
+ # rubocop:disable RSpec/MultipleExpectations
42
41
  it 'adds the full subtree' do
43
42
  expect(node[:a]).not_to be_nil
44
43
  expect(node[:a][:b]).not_to be_nil
45
44
  expect(node[:a][:b][:c]).not_to be_nil
46
45
  end
46
+ # rubocop:enable RSpec/MultipleExpectations
47
47
 
48
+ # rubocop:disable RSpec/MultipleExpectations
48
49
  it 'marks only the last child as terminal' do
49
50
  expect(node).not_to be_terminal
50
51
  expect(node[:a]).not_to be_terminal
51
52
  expect(node[:a][:b]).not_to be_terminal
52
53
  expect(node[:a][:b][:c]).to be_terminal
53
54
  end
55
+ # rubocop:enable RSpec/MultipleExpectations
54
56
  end
55
57
 
56
58
  context 'when a word is added more than once' do
@@ -59,19 +61,23 @@ describe Rambling::Trie::Nodes::Raw do
59
61
  add_word node, 'ack'
60
62
  end
61
63
 
64
+ # rubocop:disable RSpec/MultipleExpectations
62
65
  it 'only counts it once' do
63
66
  expect(node.children.size).to eq 1
64
67
  expect(node[:a].children.size).to eq 1
65
68
  expect(node[:a][:c].children.size).to eq 1
66
69
  expect(node[:a][:c][:k].children.size).to eq 0
67
70
  end
71
+ # rubocop:enable RSpec/MultipleExpectations
68
72
 
73
+ # rubocop:disable RSpec/MultipleExpectations
69
74
  it 'does not change the terminal nodes in the tree' do
70
75
  expect(node).not_to be_terminal
71
76
  expect(node[:a]).not_to be_terminal
72
77
  expect(node[:a][:c]).not_to be_terminal
73
78
  expect(node[:a][:c][:k]).to be_terminal
74
79
  end
80
+ # rubocop:enable RSpec/MultipleExpectations
75
81
 
76
82
  it 'still returns the "added" node' do
77
83
  child = add_word node, 'ack'
@@ -80,21 +86,20 @@ describe Rambling::Trie::Nodes::Raw do
80
86
  end
81
87
 
82
88
  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
89
+ before { add_words node, %w(ack a) }
86
90
 
87
91
  it 'does not add another branch' do
88
92
  expect(node.children.size).to eq 1
89
93
  end
90
94
 
95
+ # rubocop:disable RSpec/MultipleExpectations
91
96
  it 'marks the corresponding node as terminal' do
92
- expect(node[:a]).to be_terminal
93
-
94
97
  expect(node).not_to be_terminal
98
+ expect(node[:a]).to be_terminal
95
99
  expect(node[:a][:c]).not_to be_terminal
96
100
  expect(node[:a][:c][:k]).to be_terminal
97
101
  end
102
+ # rubocop:enable RSpec/MultipleExpectations
98
103
 
99
104
  it 'returns the added node' do
100
105
  child = add_word node, 'a'
@@ -103,13 +108,11 @@ describe Rambling::Trie::Nodes::Raw do
103
108
  end
104
109
 
105
110
  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 }
111
+ let(:parent) { described_class.new }
112
+ let(:node) { described_class.new :a, parent }
108
113
 
109
- context 'adding an empty string' do
110
- before do
111
- add_word node, ''
112
- end
114
+ context 'when adding an empty string' do
115
+ before { add_word node, '' }
113
116
 
114
117
  it 'does not alter the node letter' do
115
118
  expect(node.letter).to eq :a
@@ -124,19 +127,19 @@ describe Rambling::Trie::Nodes::Raw do
124
127
  end
125
128
  end
126
129
 
127
- context 'adding a one letter word' do
128
- before do
129
- add_word node, 'b'
130
- end
130
+ context 'when adding a one letter word' do
131
+ before { add_word node, 'b' }
131
132
 
132
133
  it 'does not alter the node letter' do
133
134
  expect(node.letter).to eq :a
134
135
  end
135
136
 
137
+ # rubocop:disable RSpec/MultipleExpectations
136
138
  it 'adds a child with the expected letter' do
137
139
  expect(node.children.size).to eq 1
138
140
  expect(node.children.first.letter).to eq :b
139
141
  end
142
+ # rubocop:enable RSpec/MultipleExpectations
140
143
 
141
144
  it 'reports it has the expected letter a key' do
142
145
  expect(node).to have_key(:b)
@@ -155,15 +158,14 @@ describe Rambling::Trie::Nodes::Raw do
155
158
  end
156
159
  end
157
160
 
158
- context 'adding a large word' do
159
- before do
160
- add_word node, 'mplified'
161
- end
161
+ context 'when adding a large word' do
162
+ before { add_word node, 'mplified' }
162
163
 
163
164
  it 'marks the last letter as terminal' do
164
165
  expect(node[:m][:p][:l][:i][:f][:i][:e][:d]).to be_terminal
165
166
  end
166
167
 
168
+ # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations
167
169
  it 'does not mark any other letter as terminal' do
168
170
  expect(node[:m][:p][:l][:i][:f][:i][:e]).not_to be_terminal
169
171
  expect(node[:m][:p][:l][:i][:f][:i]).not_to be_terminal
@@ -173,6 +175,7 @@ describe Rambling::Trie::Nodes::Raw do
173
175
  expect(node[:m][:p]).not_to be_terminal
174
176
  expect(node[:m]).not_to be_terminal
175
177
  end
178
+ # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations
176
179
  end
177
180
  end
178
181
  end
@@ -3,14 +3,24 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Readers::PlainText do
6
+ subject(:reader) { described_class.new }
7
+
6
8
  describe '#each_word' do
7
9
  let(:filepath) { File.join(::SPEC_ROOT, 'assets', 'test_words.en_US.txt') }
8
10
  let(:words) { File.readlines(filepath).map(&:chomp) }
9
11
 
10
12
  it 'yields every word yielded by the file' do
11
13
  yielded = []
12
- subject.each_word(filepath) { |word| yielded << word }
14
+ reader.each_word(filepath) { |word| yielded << word }
13
15
  expect(yielded).to eq words
14
16
  end
17
+
18
+ it 'returns an enumerator when no block is given' do
19
+ expect(reader.each_word filepath).to be_an Enumerator
20
+ end
21
+
22
+ it 'returns the enumerable when a block is given' do
23
+ expect(reader.each_word(filepath) { |_| }).to eq reader
24
+ end
15
25
  end
16
26
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Readers::Reader do
6
+ subject(:reader) { described_class.new }
7
+
8
+ describe '#load' do
9
+ it 'is an abstract method that raises NotImplementedError' do
10
+ expect { reader.each_word('any-file.zip') }
11
+ .to raise_exception NotImplementedError
12
+ end
13
+ end
14
+ end
@@ -4,10 +4,8 @@ require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Serializers::File do
6
6
  it_behaves_like 'a serializer' do
7
- let(:serializer) { Rambling::Trie::Serializers::File.new }
8
7
  let(:format) { :file }
9
-
10
8
  let(:content) { trie.to_a.join ' ' }
11
- let(:formatted_content) { content }
9
+ let(:format_content) { ->(content) { content } }
12
10
  end
13
11
  end
@@ -4,9 +4,7 @@ require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Serializers::Marshal do
6
6
  it_behaves_like 'a serializer' do
7
- let(:serializer) { Rambling::Trie::Serializers::Marshal.new }
8
7
  let(:format) { :marshal }
9
-
10
- let(:formatted_content) { Marshal.dump content }
8
+ let(:format_content) { Marshal.method(:dump) }
11
9
  end
12
10
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Serializers::Serializer do
6
+ subject(:serializer) { described_class.new }
7
+
8
+ describe '#load' do
9
+ it 'is an abstract method that raises NotImplementedError' do
10
+ expect { serializer.load('any-file.zip') }
11
+ .to raise_exception NotImplementedError
12
+ end
13
+ end
14
+
15
+ describe '#dump' do
16
+ it 'is an abstract method that raises NotImplementedError' do
17
+ expect { serializer.dump('any contents', 'any-file.zip') }
18
+ .to raise_exception NotImplementedError
19
+ end
20
+ end
21
+ end
@@ -4,9 +4,7 @@ require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Serializers::Yaml do
6
6
  it_behaves_like 'a serializer' do
7
- let(:serializer) { Rambling::Trie::Serializers::Yaml.new }
8
7
  let(:format) { :yml }
9
-
10
- let(:formatted_content) { YAML.dump content }
8
+ let(:format_content) { YAML.method(:dump) }
11
9
  end
12
10
  end
@@ -3,26 +3,34 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Rambling::Trie::Serializers::Zip do
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' }
6
+ {
7
+ yaml: YAML.method(:dump),
8
+ yml: YAML.method(:dump),
9
+ marshal: Marshal.method(:dump),
10
+ file: Marshal.method(:dump),
11
+ }.each do |format, dump_method|
12
+ context "with '.#{format}'" do
13
+ it_behaves_like 'a serializer' do
14
+ let(:properties) { Rambling::Trie::Configuration::Properties.new }
15
+ let(:serializer) { described_class.new properties }
16
+ let(:format) { :zip }
10
17
 
11
- before do
12
- properties.tmp_path = tmp_path
13
- end
14
-
15
- let(:filename) { File.basename(filepath).gsub %r{\.zip}, '' }
16
- let(:formatted_content) { zip Marshal.dump content }
18
+ let(:filepath) { File.join tmp_path, "trie-root.#{format}.zip" }
19
+ let(:format_content) { ->(content) { zip dump_method.call content } }
20
+ let(:filename) { File.basename(filepath).gsub %r{\.zip}, '' }
17
21
 
18
- def zip content
19
- cursor = Zip::OutputStream.write_buffer do |io|
20
- io.put_next_entry filename
21
- io.write content
22
+ before { properties.tmp_path = tmp_path }
22
23
  end
24
+ end
25
+ end
23
26
 
24
- cursor.rewind
25
- cursor.read
27
+ def zip content
28
+ cursor = Zip::OutputStream.write_buffer do |io|
29
+ io.put_next_entry filename
30
+ io.write content
26
31
  end
32
+
33
+ cursor.rewind
34
+ cursor.read
27
35
  end
28
36
  end
@@ -6,17 +6,15 @@ describe Rambling::Trie::Stringifyable do
6
6
  describe '#as_word' do
7
7
  let(:node) { Rambling::Trie::Nodes::Raw.new }
8
8
 
9
- context 'for an empty node' do
10
- before do
11
- add_word node, ''
12
- end
9
+ context 'with an empty node' do
10
+ before { add_word node, '' }
13
11
 
14
12
  it 'returns nil' do
15
13
  expect(node.as_word).to be_empty
16
14
  end
17
15
  end
18
16
 
19
- context 'for one letter' do
17
+ context 'with one letter' do
20
18
  before do
21
19
  node.letter = :a
22
20
  add_word node, ''
@@ -27,7 +25,7 @@ describe Rambling::Trie::Stringifyable do
27
25
  end
28
26
  end
29
27
 
30
- context 'for a small word' do
28
+ context 'with a small word' do
31
29
  before do
32
30
  node.letter = :a
33
31
  add_word node, 'll'
@@ -43,7 +41,7 @@ describe Rambling::Trie::Stringifyable do
43
41
  end
44
42
  end
45
43
 
46
- context 'for a long word' do
44
+ context 'with a long word' do
47
45
  before do
48
46
  node.letter = :b
49
47
  add_word node, 'eautiful'
@@ -54,7 +52,7 @@ describe Rambling::Trie::Stringifyable do
54
52
  end
55
53
  end
56
54
 
57
- context 'for a node with nil letter' do
55
+ context 'with a node with nil letter' do
58
56
  let(:node) { Rambling::Trie::Nodes::Raw.new nil }
59
57
 
60
58
  it 'returns nil' do
@@ -62,7 +60,7 @@ describe Rambling::Trie::Stringifyable do
62
60
  end
63
61
  end
64
62
 
65
- context 'for a compressed node' do
63
+ context 'with a compressed node' do
66
64
  let(:compressor) { Rambling::Trie::Compressor.new }
67
65
  let(:compressed_node) { compressor.compress node }
68
66
 
@@ -71,12 +69,18 @@ describe Rambling::Trie::Stringifyable do
71
69
  add_words node, %w(m dd)
72
70
  end
73
71
 
74
- it 'returns the words for the terminal nodes' do
75
- expect(compressed_node[:m].as_word).to eq 'am'
76
- expect(compressed_node[:d].as_word).to eq 'add'
72
+ [
73
+ [:m, 'am'],
74
+ [:d, 'add'],
75
+ ].each do |test_params|
76
+ key, expected = test_params
77
+
78
+ it "returns the words for terminal nodes (#{key} => #{expected})" do
79
+ expect(compressed_node[key].as_word).to eq expected
80
+ end
77
81
  end
78
82
 
79
- it 'raise an error for non terminal nodes' do
83
+ it 'raises an error for non terminal nodes' do
80
84
  expect { compressed_node.as_word }
81
85
  .to raise_error Rambling::Trie::InvalidOperation
82
86
  end