rambling-trie 1.0.2 → 2.2.0
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.
- checksums.yaml +5 -5
- data/Gemfile +6 -3
- data/Guardfile +3 -1
- data/README.md +30 -12
- data/Rakefile +8 -0
- data/lib/rambling-trie.rb +2 -0
- data/lib/rambling/trie.rb +48 -26
- data/lib/rambling/trie/comparable.rb +6 -3
- data/lib/rambling/trie/compressible.rb +16 -0
- data/lib/rambling/trie/compressor.rb +39 -24
- data/lib/rambling/trie/configuration.rb +3 -1
- data/lib/rambling/trie/configuration/properties.rb +18 -9
- data/lib/rambling/trie/configuration/provider_collection.rb +38 -17
- data/lib/rambling/trie/container.rb +123 -36
- data/lib/rambling/trie/enumerable.rb +6 -4
- data/lib/rambling/trie/inspectable.rb +2 -0
- data/lib/rambling/trie/invalid_operation.rb +3 -1
- data/lib/rambling/trie/nodes.rb +13 -0
- data/lib/rambling/trie/nodes/compressed.rb +98 -0
- data/lib/rambling/trie/nodes/missing.rb +12 -0
- data/lib/rambling/trie/nodes/node.rb +183 -0
- data/lib/rambling/trie/nodes/raw.rb +82 -0
- data/lib/rambling/trie/readers.rb +3 -1
- data/lib/rambling/trie/readers/plain_text.rb +3 -11
- data/lib/rambling/trie/serializers.rb +3 -1
- data/lib/rambling/trie/serializers/file.rb +2 -0
- data/lib/rambling/trie/serializers/marshal.rb +15 -5
- data/lib/rambling/trie/serializers/yaml.rb +21 -5
- data/lib/rambling/trie/serializers/zip.rb +15 -8
- data/lib/rambling/trie/stringifyable.rb +8 -2
- data/lib/rambling/trie/version.rb +3 -1
- data/rambling-trie.gemspec +21 -10
- data/spec/assets/test_words.es_DO.txt +1 -0
- data/spec/integration/rambling/trie_spec.rb +44 -35
- data/spec/lib/rambling/trie/comparable_spec.rb +8 -15
- data/spec/lib/rambling/trie/compressor_spec.rb +90 -13
- data/spec/lib/rambling/trie/configuration/properties_spec.rb +21 -13
- data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +18 -34
- data/spec/lib/rambling/trie/container_spec.rb +183 -217
- data/spec/lib/rambling/trie/enumerable_spec.rb +14 -9
- data/spec/lib/rambling/trie/inspectable_spec.rb +36 -11
- data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
- data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
- data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
- data/spec/lib/rambling/trie/readers/plain_text_spec.rb +3 -1
- data/spec/lib/rambling/trie/serializers/file_spec.rb +6 -4
- data/spec/lib/rambling/trie/serializers/marshal_spec.rb +5 -7
- data/spec/lib/rambling/trie/serializers/yaml_spec.rb +5 -7
- data/spec/lib/rambling/trie/serializers/zip_spec.rb +18 -20
- data/spec/lib/rambling/trie/stringifyable_spec.rb +14 -11
- data/spec/lib/rambling/trie_spec.rb +18 -11
- data/spec/spec_helper.rb +10 -5
- data/spec/support/config.rb +10 -0
- data/spec/support/helpers/add_word.rb +20 -0
- data/spec/support/helpers/one_line_heredoc.rb +11 -0
- data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
- data/spec/support/shared_examples/a_serializable_trie.rb +10 -6
- data/spec/support/shared_examples/a_serializer.rb +9 -1
- data/spec/support/shared_examples/a_trie_data_structure.rb +2 -0
- data/spec/support/shared_examples/a_trie_node.rb +127 -0
- data/spec/{lib/rambling/trie/compressed_node_spec.rb → support/shared_examples/a_trie_node_implementation.rb} +25 -72
- metadata +42 -31
- data/lib/rambling/trie/compressable.rb +0 -14
- data/lib/rambling/trie/compressed_node.rb +0 -120
- data/lib/rambling/trie/missing_node.rb +0 -8
- data/lib/rambling/trie/node.rb +0 -97
- data/lib/rambling/trie/raw_node.rb +0 -96
- data/spec/lib/rambling/trie/node_spec.rb +0 -86
- data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
- data/spec/support/shared_examples/a_compressable_trie.rb +0 -26
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Rambling::Trie::Stringifyable do
|
4
6
|
describe '#as_word' do
|
5
|
-
let(:node) { Rambling::Trie::
|
7
|
+
let(:node) { Rambling::Trie::Nodes::Raw.new }
|
6
8
|
|
7
9
|
context 'for an empty node' do
|
8
10
|
before do
|
9
|
-
node
|
11
|
+
add_word node, ''
|
10
12
|
end
|
11
13
|
|
12
14
|
it 'returns nil' do
|
@@ -17,7 +19,7 @@ describe Rambling::Trie::Stringifyable do
|
|
17
19
|
context 'for one letter' do
|
18
20
|
before do
|
19
21
|
node.letter = :a
|
20
|
-
node
|
22
|
+
add_word node, ''
|
21
23
|
end
|
22
24
|
|
23
25
|
it 'returns the expected one letter word' do
|
@@ -28,7 +30,7 @@ describe Rambling::Trie::Stringifyable do
|
|
28
30
|
context 'for a small word' do
|
29
31
|
before do
|
30
32
|
node.letter = :a
|
31
|
-
node
|
33
|
+
add_word node, 'll'
|
32
34
|
end
|
33
35
|
|
34
36
|
it 'returns the expected small word' do
|
@@ -36,14 +38,15 @@ describe Rambling::Trie::Stringifyable do
|
|
36
38
|
end
|
37
39
|
|
38
40
|
it 'raises an error for a non terminal node' do
|
39
|
-
expect { node[:l].as_word }
|
41
|
+
expect { node[:l].as_word }
|
42
|
+
.to raise_error Rambling::Trie::InvalidOperation
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
46
|
context 'for a long word' do
|
44
47
|
before do
|
45
48
|
node.letter = :b
|
46
|
-
node
|
49
|
+
add_word node, 'eautiful'
|
47
50
|
end
|
48
51
|
|
49
52
|
it 'returns the expected long word' do
|
@@ -52,7 +55,7 @@ describe Rambling::Trie::Stringifyable do
|
|
52
55
|
end
|
53
56
|
|
54
57
|
context 'for a node with nil letter' do
|
55
|
-
let(:node) { Rambling::Trie::
|
58
|
+
let(:node) { Rambling::Trie::Nodes::Raw.new nil }
|
56
59
|
|
57
60
|
it 'returns nil' do
|
58
61
|
expect(node.as_word).to be_empty
|
@@ -65,17 +68,17 @@ describe Rambling::Trie::Stringifyable do
|
|
65
68
|
|
66
69
|
before do
|
67
70
|
node.letter = :a
|
68
|
-
node
|
69
|
-
node.add 'dd'
|
71
|
+
add_words node, %w(m dd)
|
70
72
|
end
|
71
73
|
|
72
74
|
it 'returns the words for the terminal nodes' do
|
73
75
|
expect(compressed_node[:m].as_word).to eq 'am'
|
74
|
-
expect(compressed_node[:
|
76
|
+
expect(compressed_node[:d].as_word).to eq 'add'
|
75
77
|
end
|
76
78
|
|
77
79
|
it 'raise an error for non terminal nodes' do
|
78
|
-
expect { compressed_node.as_word }
|
80
|
+
expect { compressed_node.as_word }
|
81
|
+
.to raise_error Rambling::Trie::InvalidOperation
|
79
82
|
end
|
80
83
|
end
|
81
84
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Rambling::Trie do
|
4
6
|
describe '.create' do
|
5
|
-
let(:root) { Rambling::Trie::
|
7
|
+
let(:root) { Rambling::Trie::Nodes::Raw.new }
|
6
8
|
let(:compressor) { Rambling::Trie::Compressor.new }
|
7
9
|
let!(:container) { Rambling::Trie::Container.new root, compressor }
|
8
10
|
|
@@ -31,7 +33,10 @@ describe Rambling::Trie do
|
|
31
33
|
|
32
34
|
before do
|
33
35
|
receive_and_yield = receive(:each_word)
|
34
|
-
words.inject(receive_and_yield)
|
36
|
+
words.inject(receive_and_yield) do |yielder, word|
|
37
|
+
yielder.and_yield word
|
38
|
+
end
|
39
|
+
|
35
40
|
allow(reader).to receive_and_yield
|
36
41
|
allow(container).to receive :<<
|
37
42
|
end
|
@@ -40,7 +45,7 @@ describe Rambling::Trie do
|
|
40
45
|
Rambling::Trie.create filepath, reader
|
41
46
|
|
42
47
|
words.each do |word|
|
43
|
-
expect(container).to have_received(:<<).with
|
48
|
+
expect(container).to have_received(:<<).with word
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
@@ -65,7 +70,7 @@ describe Rambling::Trie do
|
|
65
70
|
end
|
66
71
|
|
67
72
|
describe '.load' do
|
68
|
-
let(:root) { Rambling::Trie::
|
73
|
+
let(:root) { Rambling::Trie::Nodes::Raw.new }
|
69
74
|
let(:compressor) { Rambling::Trie::Compressor.new }
|
70
75
|
let(:container) { Rambling::Trie::Container.new root, compressor }
|
71
76
|
let(:serializer) { double :serializer, load: root }
|
@@ -77,7 +82,7 @@ describe Rambling::Trie do
|
|
77
82
|
end
|
78
83
|
|
79
84
|
it 'uses the serializer to load the root node from the given filepath' do
|
80
|
-
|
85
|
+
Rambling::Trie.load filepath, serializer
|
81
86
|
expect(serializer).to have_received(:load).with filepath
|
82
87
|
end
|
83
88
|
|
@@ -98,16 +103,16 @@ describe Rambling::Trie do
|
|
98
103
|
end
|
99
104
|
|
100
105
|
it 'determines the serializer based on the file extension' do
|
101
|
-
|
106
|
+
Rambling::Trie.load 'test.marshal'
|
102
107
|
expect(marshal_serializer).to have_received(:load).with 'test.marshal'
|
103
108
|
|
104
|
-
|
109
|
+
Rambling::Trie.load 'test.yml'
|
105
110
|
expect(yaml_serializer).to have_received(:load).with 'test.yml'
|
106
111
|
|
107
|
-
|
112
|
+
Rambling::Trie.load 'test.yaml'
|
108
113
|
expect(yaml_serializer).to have_received(:load).with 'test.yaml'
|
109
114
|
|
110
|
-
|
115
|
+
Rambling::Trie.load 'test'
|
111
116
|
expect(default_serializer).to have_received(:load).with 'test'
|
112
117
|
end
|
113
118
|
end
|
@@ -153,10 +158,12 @@ describe Rambling::Trie do
|
|
153
158
|
context 'when provided with a format' do
|
154
159
|
it 'uses the corresponding serializer' do
|
155
160
|
Rambling::Trie.dump trie, "#{filename}.marshal"
|
156
|
-
expect(marshal_serializer).to have_received(:dump)
|
161
|
+
expect(marshal_serializer).to have_received(:dump)
|
162
|
+
.with root, "#{filename}.marshal"
|
157
163
|
|
158
164
|
Rambling::Trie.dump trie, "#{filename}.yml"
|
159
|
-
expect(yaml_serializer).to have_received(:dump)
|
165
|
+
expect(yaml_serializer).to have_received(:dump)
|
166
|
+
.with root, "#{filename}.yml"
|
160
167
|
end
|
161
168
|
end
|
162
169
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'simplecov'
|
2
4
|
require 'coveralls'
|
3
5
|
|
@@ -5,7 +7,7 @@ Coveralls.wear!
|
|
5
7
|
|
6
8
|
SimpleCov.formatters = [
|
7
9
|
SimpleCov::Formatter::HTMLFormatter,
|
8
|
-
Coveralls::SimpleCov::Formatter
|
10
|
+
Coveralls::SimpleCov::Formatter,
|
9
11
|
]
|
10
12
|
|
11
13
|
SimpleCov.start do
|
@@ -26,7 +28,10 @@ RSpec.configure do |config|
|
|
26
28
|
end
|
27
29
|
|
28
30
|
require 'support/config'
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
|
32
|
+
%w(
|
33
|
+
a_compressible_trie a_serializable_trie a_serializer a_trie_data_structure
|
34
|
+
a_trie_node a_trie_node_implementation
|
35
|
+
).each do |name|
|
36
|
+
require File.join('support', 'shared_examples', name)
|
37
|
+
end
|
data/spec/support/config.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helpers/add_word'
|
4
|
+
require_relative 'helpers/one_line_heredoc'
|
5
|
+
|
1
6
|
RSpec.configure do |c|
|
2
7
|
c.before do
|
3
8
|
Rambling::Trie.config.reset
|
4
9
|
end
|
10
|
+
|
11
|
+
c.include Support::Helpers::AddWord
|
12
|
+
c.include Support::Helpers::OneLineHeredoc
|
13
|
+
|
14
|
+
RSpec::Matchers.define_negated_matcher :not_change, :change
|
5
15
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Support
|
4
|
+
module Helpers
|
5
|
+
module AddWord
|
6
|
+
def add_words node, words
|
7
|
+
words.each { |word| add_word node, word }
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_word node, word
|
11
|
+
case node
|
12
|
+
when Rambling::Trie::Container
|
13
|
+
node.add word
|
14
|
+
else
|
15
|
+
node.add word.chars.reverse.map(&:to_sym)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
shared_examples_for 'a compressible trie' do
|
4
|
+
context 'and the trie is not compressed' do
|
5
|
+
it_behaves_like 'a trie data structure'
|
6
|
+
|
7
|
+
it 'does not alter the input' do
|
8
|
+
word = 'string'
|
9
|
+
add_word trie, word
|
10
|
+
|
11
|
+
expect(word).to eq 'string'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'is marked as not compressed' do
|
15
|
+
expect(trie).not_to be_compressed
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'and the trie is compressed' do
|
20
|
+
let!(:original_root) { trie.root }
|
21
|
+
let!(:original_keys) { original_root.children_tree.keys }
|
22
|
+
let!(:original_values) { original_root.children_tree.values }
|
23
|
+
|
24
|
+
before do
|
25
|
+
trie.compress!
|
26
|
+
end
|
27
|
+
|
28
|
+
it_behaves_like 'a trie data structure'
|
29
|
+
|
30
|
+
it 'is marked as compressed' do
|
31
|
+
expect(trie).to be_compressed
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'leaves the original root intact' do
|
35
|
+
expect(original_root.children_tree.keys).to eq original_keys
|
36
|
+
expect(trie.children_tree.keys).to eq original_keys
|
37
|
+
expect(trie.children_tree.values).not_to eq original_values
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,20 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
shared_examples_for 'a serializable trie' do
|
4
|
+
let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
|
5
|
+
let(:filepath) { File.join tmp_path, "trie-root.#{format}" }
|
6
|
+
|
2
7
|
context 'and the trie is not compressed' do
|
3
8
|
before do
|
4
|
-
Rambling::Trie.dump trie_to_serialize,
|
9
|
+
Rambling::Trie.dump trie_to_serialize, filepath
|
5
10
|
end
|
6
11
|
|
7
|
-
it_behaves_like 'a
|
8
|
-
let(:trie) {
|
12
|
+
it_behaves_like 'a compressible trie' do
|
13
|
+
let(:trie) { Rambling::Trie.load filepath }
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
12
17
|
context 'and the trie is compressed' do
|
13
|
-
let(:trie) {
|
18
|
+
let(:trie) { Rambling::Trie.load filepath }
|
14
19
|
|
15
20
|
before do
|
16
|
-
|
17
|
-
Rambling::Trie.dump trie_to_serialize.compress!, trie_filepath, serializer
|
21
|
+
Rambling::Trie.dump trie_to_serialize.compress!, filepath
|
18
22
|
end
|
19
23
|
|
20
24
|
it_behaves_like 'a trie data structure'
|
@@ -1,5 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
shared_examples_for 'a serializer' do
|
4
|
+
let(:trie) { Rambling::Trie.create }
|
5
|
+
let(:tmp_path) { File.join ::SPEC_ROOT, 'tmp' }
|
6
|
+
let(:filepath) { File.join tmp_path, "trie-root.#{format}" }
|
7
|
+
let(:content) { trie.root }
|
8
|
+
|
2
9
|
before do
|
10
|
+
trie.concat %w(a few words to validate that load and dump are working)
|
3
11
|
FileUtils.rm_f filepath
|
4
12
|
end
|
5
13
|
|
@@ -13,7 +21,7 @@ shared_examples_for 'a serializer' do
|
|
13
21
|
end
|
14
22
|
|
15
23
|
it 'converts the contents to the appropriate format' do
|
16
|
-
expect(File.read(filepath).size).to
|
24
|
+
expect(File.read(filepath).size).to be_within(10).of formatted_content.size
|
17
25
|
end
|
18
26
|
end
|
19
27
|
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
shared_examples_for 'a trie node' do
|
4
|
+
let(:node_class) { node.class }
|
5
|
+
|
6
|
+
describe '.new' do
|
7
|
+
it 'has no letter' do
|
8
|
+
expect(node.letter).to be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'has no children' do
|
12
|
+
expect(node.children.size).to eq 0
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'is not terminal' do
|
16
|
+
expect(node).not_to be_terminal
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns empty string as its word' do
|
20
|
+
expect(node.as_word).to be_empty
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with a letter and a parent' do
|
24
|
+
let(:parent) { node.class.new }
|
25
|
+
let(:node_with_parent) { node_class.new :a, parent }
|
26
|
+
|
27
|
+
it 'does not have any letter' do
|
28
|
+
expect(node_with_parent.letter).to eq :a
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has no children' do
|
32
|
+
expect(node_with_parent.children.size).to eq 0
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'is not terminal' do
|
36
|
+
expect(node_with_parent).not_to be_terminal
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#root?' do
|
42
|
+
context 'when the node has a parent' do
|
43
|
+
before do
|
44
|
+
node.parent = node
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns false' do
|
48
|
+
expect(node).not_to be_root
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when the node does not have a parent' do
|
53
|
+
before do
|
54
|
+
node.parent = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns true' do
|
58
|
+
expect(node).to be_root
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#terminal!' do
|
64
|
+
it 'forces the node to be terminal' do
|
65
|
+
expect(node).not_to be_terminal
|
66
|
+
node.terminal!
|
67
|
+
|
68
|
+
expect(node).to be_terminal
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns the node' do
|
72
|
+
expect(node.terminal!).to eq node
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'delegates and aliases' do
|
77
|
+
let(:children_tree) do
|
78
|
+
double(
|
79
|
+
:children_tree,
|
80
|
+
:[] => 'value',
|
81
|
+
:[]= => nil,
|
82
|
+
key?: false,
|
83
|
+
delete: true,
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
before do
|
88
|
+
node.children_tree = children_tree
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'delegates `#[]` to its children tree' do
|
92
|
+
expect(node[:key]).to eq 'value'
|
93
|
+
expect(children_tree).to have_received(:[]).with :key
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'delegates `#[]=` to its children tree' do
|
97
|
+
node[:key] = 'value'
|
98
|
+
expect(children_tree).to have_received(:[]=).with(:key, 'value')
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'delegates `#key?` to its children tree' do
|
102
|
+
allow(children_tree).to receive(:key?)
|
103
|
+
.with(:present_key)
|
104
|
+
.and_return true
|
105
|
+
|
106
|
+
expect(node).to have_key(:present_key)
|
107
|
+
expect(node).not_to have_key(:absent_key)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'delegates `#delete` to its children tree' do
|
111
|
+
expect(node.delete :key).to be true
|
112
|
+
expect(children_tree).to have_received(:delete).with :key
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'delegates `#children` to its children tree values' do
|
116
|
+
children = [double(:child_1), double(:child_2)]
|
117
|
+
allow(children_tree).to receive(:values).and_return children
|
118
|
+
|
119
|
+
expect(node.children).to eq children
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'aliases `#has_key?` to `#key?`' do
|
123
|
+
node.has_key? :nope
|
124
|
+
expect(children_tree).to have_received(:key?).with :nope
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|