rambling-trie 2.2.1 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
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