rambling-trie 0.9.3 → 1.0.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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/LICENSE +1 -1
  4. data/README.md +133 -26
  5. data/Rakefile +1 -2
  6. data/lib/rambling/trie.rb +53 -9
  7. data/lib/rambling/trie/comparable.rb +16 -0
  8. data/lib/rambling/trie/compressable.rb +14 -0
  9. data/lib/rambling/trie/compressed_node.rb +38 -14
  10. data/lib/rambling/trie/compressor.rb +14 -10
  11. data/lib/rambling/trie/configuration.rb +11 -0
  12. data/lib/rambling/trie/configuration/properties.rb +66 -0
  13. data/lib/rambling/trie/configuration/provider_collection.rb +101 -0
  14. data/lib/rambling/trie/container.rb +57 -17
  15. data/lib/rambling/trie/enumerable.rb +1 -1
  16. data/lib/rambling/trie/forwardable.rb +9 -4
  17. data/lib/rambling/trie/inspectable.rb +37 -0
  18. data/lib/rambling/trie/invalid_operation.rb +3 -2
  19. data/lib/rambling/trie/missing_node.rb +2 -1
  20. data/lib/rambling/trie/node.rb +40 -30
  21. data/lib/rambling/trie/raw_node.rb +29 -13
  22. data/lib/rambling/trie/readers.rb +11 -0
  23. data/lib/rambling/trie/readers/plain_text.rb +26 -0
  24. data/lib/rambling/trie/serializers.rb +11 -0
  25. data/lib/rambling/trie/serializers/file.rb +25 -0
  26. data/lib/rambling/trie/serializers/marshal.rb +38 -0
  27. data/lib/rambling/trie/serializers/yaml.rb +39 -0
  28. data/lib/rambling/trie/serializers/zip.rb +67 -0
  29. data/lib/rambling/trie/stringifyable.rb +20 -0
  30. data/lib/rambling/trie/version.rb +1 -1
  31. data/rambling-trie.gemspec +2 -2
  32. data/spec/integration/rambling/trie_spec.rb +45 -49
  33. data/spec/lib/rambling/trie/comparable_spec.rb +104 -0
  34. data/spec/lib/rambling/trie/compressed_node_spec.rb +44 -0
  35. data/spec/lib/rambling/trie/configuration/properties_spec.rb +49 -0
  36. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +165 -0
  37. data/spec/lib/rambling/trie/container_spec.rb +127 -38
  38. data/spec/lib/rambling/trie/{inspector_spec.rb → inspectable_spec.rb} +7 -5
  39. data/spec/lib/rambling/trie/raw_node_spec.rb +22 -41
  40. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +14 -0
  41. data/spec/lib/rambling/trie/serializers/file_spec.rb +11 -0
  42. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +14 -0
  43. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +14 -0
  44. data/spec/lib/rambling/trie/serializers/zip_spec.rb +30 -0
  45. data/spec/lib/rambling/trie/stringifyable_spec.rb +82 -0
  46. data/spec/lib/rambling/trie_spec.rb +120 -7
  47. data/spec/spec_helper.rb +7 -1
  48. data/spec/support/config.rb +5 -0
  49. data/spec/support/shared_examples/a_compressable_trie.rb +26 -0
  50. data/spec/support/shared_examples/a_serializable_trie.rb +26 -0
  51. data/spec/support/shared_examples/a_serializer.rb +29 -0
  52. data/spec/support/shared_examples/a_trie_data_structure.rb +29 -0
  53. data/spec/tmp/.gitkeep +0 -0
  54. metadata +51 -24
  55. data/lib/rambling/trie/compression.rb +0 -13
  56. data/lib/rambling/trie/inspector.rb +0 -11
  57. data/lib/rambling/trie/plain_text_reader.rb +0 -23
  58. data/lib/rambling/trie/tasks/gem.rb +0 -17
  59. data/lib/rambling/trie/tasks/helpers/path.rb +0 -17
  60. data/lib/rambling/trie/tasks/helpers/performance_report.rb +0 -17
  61. data/lib/rambling/trie/tasks/helpers/time.rb +0 -7
  62. data/lib/rambling/trie/tasks/performance.rb +0 -15
  63. data/lib/rambling/trie/tasks/performance/all.rb +0 -17
  64. data/lib/rambling/trie/tasks/performance/benchmark.rb +0 -201
  65. data/lib/rambling/trie/tasks/performance/directory.rb +0 -11
  66. data/lib/rambling/trie/tasks/performance/flamegraph.rb +0 -119
  67. data/lib/rambling/trie/tasks/performance/profile/call_tree.rb +0 -147
  68. data/lib/rambling/trie/tasks/performance/profile/memory.rb +0 -143
  69. data/spec/lib/rambling/trie/plain_text_reader_spec.rb +0 -18
@@ -1,6 +1,6 @@
1
1
  module Rambling
2
2
  module Trie
3
3
  # Current version of the rambling-trie.
4
- VERSION = '0.9.3'
4
+ VERSION = '1.0.0'.freeze
5
5
  end
6
6
  end
@@ -5,8 +5,8 @@ require 'rambling/trie/version'
5
5
  Gem::Specification.new do |gem|
6
6
  gem.authors = ['Edgar Gonzalez', 'Lilibeth De La Cruz']
7
7
  gem.email = ['edggonzalezg@gmail.com', 'lilibethdlc@gmail.com']
8
- gem.description = 'The Rambling Trie is a custom implementation of the Trie data structure with Ruby, which includes compression abilities and is designed to be very fast to traverse.'
9
- gem.summary = 'A custom implementation of the trie data structure.'
8
+ gem.description = 'The Rambling Trie is a Ruby implementation of the trie data structure, which includes compression abilities and is designed to be very fast to traverse.'
9
+ gem.summary = 'A Ruby implementation of the trie data structure.'
10
10
  gem.homepage = 'http://github.com/gonzedge/rambling-trie'
11
11
  gem.date = Time.now.strftime '%Y-%m-%d'
12
12
 
@@ -1,55 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Rambling::Trie do
4
- shared_examples_for 'a compressable trie' do
5
- context 'and the trie is not compressed' do
6
- it_behaves_like 'a trie data structure'
7
-
8
- it 'does not alter the input' do
9
- word = 'string'
10
- trie.add word
11
-
12
- expect(word).to eq 'string'
13
- end
14
-
15
- it 'is marked as not compressed' do
16
- expect(trie).not_to be_compressed
17
- end
18
- end
19
-
20
- context 'and the trie is compressed' do
21
- before { trie.compress! }
22
-
23
- it_behaves_like 'a trie data structure'
24
-
25
- it 'is marked as compressed' do
26
- expect(trie).to be_compressed
27
- end
28
- end
29
- end
30
-
31
- shared_examples_for 'a trie data structure' do
32
- it 'contains all the words from the file' do
33
- words.each do |word|
34
- expect(trie).to include word
35
- expect(trie.word? word).to be true
36
- end
37
- end
38
-
39
- it 'matches the start of all the words from the file' do
40
- words.each do |word|
41
- expect(trie.match? word).to be true
42
- expect(trie.match? word[0..-2]).to be true
43
- expect(trie.partial_word? word).to be true
44
- expect(trie.partial_word? word[0..-2]).to be true
45
- end
46
- end
47
-
48
- it 'allows iterating over all the words' do
49
- expect(trie.to_a.sort).to eq words.sort
50
- end
51
- end
52
-
53
4
  describe 'with words provided directly' do
54
5
  it_behaves_like 'a compressable trie' do
55
6
  let(:words) { %w[a couple of words for our full trie integration test] }
@@ -79,4 +30,49 @@ describe Rambling::Trie do
79
30
  let(:trie) { Rambling::Trie.create filepath }
80
31
  end
81
32
  end
33
+
34
+ describe 'saving and loading full trie from a file' do
35
+ let(:words_filepath) { File.join ::SPEC_ROOT, 'assets', 'test_words.en_US.txt' }
36
+ let(:words) { File.readlines(words_filepath).map &:chomp! }
37
+ let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
38
+ let(:trie_filename) { File.join ::SPEC_ROOT, '..', 'tmp', 'trie-root' }
39
+
40
+ context 'when serialized with Ruby marshal format (default)' do
41
+ it_behaves_like 'a serializable trie' do
42
+ let(:trie_filepath) { "#{trie_filename}.marshal" }
43
+ let(:loaded_trie) { Rambling::Trie.load trie_filepath }
44
+ let(:serializer) { nil }
45
+ end
46
+ end
47
+
48
+ context 'when serialized with YAML' do
49
+ it_behaves_like 'a serializable trie' do
50
+ let(:trie_filepath) { "#{trie_filename}.yml" }
51
+ let(:loaded_trie) { Rambling::Trie.load trie_filepath }
52
+ let(:serializer) { nil }
53
+ end
54
+ end
55
+
56
+ context 'when serialized with zipped Ruby marshal format' do
57
+ before do
58
+ require 'zip'
59
+ @original_on_exists_proc = ::Zip.on_exists_proc
60
+ @original_continue_on_exists_proc = ::Zip.continue_on_exists_proc
61
+ ::Zip.on_exists_proc = true
62
+ ::Zip.continue_on_exists_proc = true
63
+ end
64
+
65
+ after do
66
+ require 'zip'
67
+ ::Zip.on_exists_proc = @original_on_exists_proc
68
+ ::Zip.continue_on_exists_proc = @original_continue_on_exists_proc
69
+ end
70
+
71
+ it_behaves_like 'a serializable trie' do
72
+ let(:trie_filepath) { "#{trie_filename}.marshal.zip" }
73
+ let(:loaded_trie) { Rambling::Trie.load trie_filepath }
74
+ let(:serializer) { nil }
75
+ end
76
+ end
77
+ end
82
78
  end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Comparable do
4
+ describe '#==' do
5
+ let(:node_1) { Rambling::Trie::RawNode.new }
6
+ let(:node_2) { Rambling::Trie::RawNode.new }
7
+
8
+ context 'when the nodes do not have the same letter' do
9
+ before do
10
+ node_1.letter = :a
11
+ node_2.letter = :b
12
+ end
13
+
14
+ it 'returns false' do
15
+ expect(node_1).not_to eq node_2
16
+ end
17
+ end
18
+
19
+ context 'when the nodes have the same letter and no children' do
20
+ before do
21
+ node_1.letter = :a
22
+ node_2.letter = :a
23
+ end
24
+
25
+ it 'returns true' do
26
+ expect(node_1).to eq node_2
27
+ end
28
+ end
29
+
30
+ context 'when the nodes have the same letter and are terminal' do
31
+ before do
32
+ node_1.letter = :a
33
+ node_1.terminal!
34
+
35
+ node_2.letter = :a
36
+ node_2.terminal!
37
+ end
38
+
39
+ it 'returns true' do
40
+ expect(node_1).to eq node_2
41
+ end
42
+ end
43
+
44
+ context 'when the nodes have the same letter and are not terminal' do
45
+ before do
46
+ node_1.letter = :a
47
+ node_2.letter = :a
48
+ end
49
+
50
+ it 'returns true' do
51
+ expect(node_1).to eq node_2
52
+ end
53
+ end
54
+
55
+ context 'when the nodes have the same letter but are not both terminal' do
56
+ before do
57
+ node_1.letter = :a
58
+ node_1.terminal!
59
+
60
+ node_2.letter = :a
61
+ end
62
+ it 'returns false' do
63
+ expect(node_1).not_to eq node_2
64
+ end
65
+ end
66
+
67
+ context 'when the nodes have the same letter and the same children' do
68
+ before do
69
+ node_1.letter = :t
70
+ node_1.add 'hese'
71
+ node_1.add 'hree'
72
+ node_1.add 'hings'
73
+
74
+ node_2.letter = :t
75
+ node_2.add 'hese'
76
+ node_2.add 'hree'
77
+ node_2.add 'hings'
78
+ end
79
+
80
+ it 'returns true' do
81
+ expect(node_1).to eq node_2
82
+ expect(node_1[:h][:e][:s][:e]).to eq node_2[:h][:e][:s][:e]
83
+ end
84
+ end
85
+
86
+ context 'when the nodes have the same letter but different children' do
87
+ before do
88
+ node_1.letter = :t
89
+ node_1.add 'hese'
90
+ node_1.add 'wo'
91
+
92
+ node_2.letter = :t
93
+ node_2.add 'hese'
94
+ node_2.add 'hree'
95
+ node_2.add 'hings'
96
+ end
97
+
98
+ it 'returns false' do
99
+ expect(node_1).not_to eq node_2
100
+ expect(node_1[:h][:e][:s][:e]).to eq node_2[:h][:e][:s][:e]
101
+ end
102
+ end
103
+ end
104
+ end
@@ -152,4 +152,48 @@ describe Rambling::Trie::CompressedNode do
152
152
  end
153
153
  end
154
154
  end
155
+
156
+ describe '#match_prefix' do
157
+ let(:raw_node) { Rambling::Trie::RawNode.new }
158
+ let(:compressor) { Rambling::Trie::Compressor.new }
159
+ let(:node) { compressor.compress raw_node }
160
+
161
+ before do
162
+ raw_node.letter = :i
163
+ raw_node.add 'gnite'
164
+ raw_node.add 'mport'
165
+ raw_node.add 'mportant'
166
+ raw_node.add 'mportantly'
167
+ end
168
+
169
+ context 'when the node is terminal' do
170
+ before do
171
+ raw_node.terminal!
172
+ end
173
+
174
+ it 'adds itself to the words' do
175
+ expect(node.match_prefix %w(g n i t e)).to include 'i'
176
+ end
177
+ end
178
+
179
+ context 'when the node is not terminal' do
180
+ it 'does not add itself to the words' do
181
+ expect(node.match_prefix %w(g n i t e)).not_to include 'i'
182
+ end
183
+ end
184
+
185
+ context 'when the first few chars match a terminal node' do
186
+ it 'adds those terminal nodes to the words' do
187
+ words = node.match_prefix(%w(m p o r t a n t l y)).to_a
188
+ expect(words).to include 'import', 'important', 'importantly'
189
+ end
190
+ end
191
+
192
+ context 'when the first few chars do not match a terminal node' do
193
+ it 'does not add any other words found' do
194
+ words = node.match_prefix(%w(m p m p o r t a n t l y)).to_a
195
+ expect(words).not_to include 'import', 'important', 'importantly'
196
+ end
197
+ end
198
+ end
155
199
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Configuration::Properties do
4
+ let(:properties) { Rambling::Trie::Configuration::Properties.new }
5
+
6
+ describe '.new' do
7
+ it 'configures the serializers' do
8
+ serializers = properties.serializers
9
+ expect(serializers.keys).to match_array %i(marshal yaml yml zip)
10
+
11
+ expect(serializers[:marshal]).to be_instance_of Rambling::Trie::Serializers::Marshal
12
+ expect(serializers[:yaml]).to be_instance_of Rambling::Trie::Serializers::Yaml
13
+ expect(serializers[:yml]).to be_instance_of Rambling::Trie::Serializers::Yaml
14
+ expect(serializers[:zip]).to be_instance_of Rambling::Trie::Serializers::Zip
15
+ end
16
+
17
+ it 'configures the readers' do
18
+ readers = properties.readers
19
+ expect(readers.keys).to match_array %i(txt)
20
+
21
+ expect(readers[:txt]).to be_instance_of Rambling::Trie::Readers::PlainText
22
+ end
23
+
24
+ it 'configures the compressor' do
25
+ expect(properties.compressor).to be_instance_of Rambling::Trie::Compressor
26
+ end
27
+
28
+ it 'configures the root_builder' do
29
+ expect(properties.root_builder.call).to be_instance_of Rambling::Trie::RawNode
30
+ end
31
+ end
32
+
33
+ describe '#reset' do
34
+ before do
35
+ properties.serializers.add :test, 'test'
36
+ properties.readers.add :test, 'test'
37
+ end
38
+
39
+ it 'resets the serializers and readers to initial values' do
40
+ expect(properties.serializers.keys).to include :test
41
+ expect(properties.readers.keys).to include :test
42
+
43
+ properties.reset
44
+
45
+ expect(properties.serializers.keys).not_to include :test
46
+ expect(properties.readers.keys).not_to include :test
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie::Configuration::ProviderCollection do
4
+ let(:configured_default) { nil }
5
+ let(:configured_providers) do
6
+ { one: first_provider, two: second_provider }
7
+ end
8
+
9
+ let(:first_provider) { double :first_provider }
10
+ let(:second_provider) { double :second_provider }
11
+
12
+ let(:provider_collection) do
13
+ Rambling::Trie::Configuration::ProviderCollection.new(
14
+ 'provider',
15
+ configured_providers,
16
+ configured_default
17
+ )
18
+ end
19
+
20
+ describe '.new' do
21
+ it 'has a name' do
22
+ expect(provider_collection.name).to eq 'provider'
23
+ end
24
+
25
+ it 'has the given providers' do
26
+ expect(provider_collection.providers)
27
+ .to eq one: first_provider, two: second_provider
28
+ end
29
+
30
+ it 'has a default provider' do
31
+ expect(provider_collection.default).to eq first_provider
32
+ end
33
+
34
+ context 'when a default is provided' do
35
+ let(:configured_default) { second_provider }
36
+
37
+ it 'has that as the default provider' do
38
+ expect(provider_collection.default).to eq second_provider
39
+ end
40
+ end
41
+ end
42
+
43
+ describe 'aliases and delegates' do
44
+ let(:providers) { provider_collection.providers }
45
+
46
+ before do
47
+ allow(providers) .to receive_messages(
48
+ :[] => nil,
49
+ :[]= => nil,
50
+ keys: nil,
51
+ values: nil,
52
+ )
53
+ end
54
+
55
+ it 'delegates #[] to providers' do
56
+ provider_collection[:key]
57
+ expect(providers).to have_received(:[]).with :key
58
+ end
59
+
60
+ it 'delegates #[]= to providers' do
61
+ provider_collection[:key] = 'hello'
62
+ expect(providers).to have_received(:[]=).with :key, 'hello'
63
+ end
64
+
65
+ it 'delegates #keys to providers' do
66
+ provider_collection.keys
67
+ expect(providers).to have_received :keys
68
+ end
69
+
70
+ it 'delegates #values to providers' do
71
+ provider_collection.values
72
+ expect(providers).to have_received :values
73
+ end
74
+ end
75
+
76
+ describe '#add' do
77
+ let(:provider) { double :provider }
78
+
79
+ before do
80
+ provider_collection.add :three, provider
81
+ end
82
+
83
+ it 'adds a new provider' do
84
+ expect(provider_collection.providers[:three]).to eq provider
85
+ end
86
+ end
87
+
88
+ describe '#default=' do
89
+ let(:other_provider) { double :other_provider }
90
+
91
+ context 'when the given value is in the providers list' do
92
+ it 'changes the default provider' do
93
+ provider_collection.default = second_provider
94
+ expect(provider_collection.default).to eq second_provider
95
+ end
96
+ end
97
+
98
+ context 'when the given value is not in the providers list' do
99
+ it 'does not change the default provider' do
100
+ expect do
101
+ begin
102
+ provider_collection.default = other_provider
103
+ rescue
104
+ end
105
+ end.not_to change { provider_collection.default }
106
+ end
107
+
108
+ it 'raises an ArgumentError' do
109
+ expect do
110
+ provider_collection.default = other_provider
111
+ end.to raise_error ArgumentError
112
+ end
113
+ end
114
+
115
+ context 'when the providers list is empty' do
116
+ let(:configured_providers) do
117
+ {}
118
+ end
119
+
120
+ it 'accepts nil' do
121
+ provider_collection.default = nil
122
+ expect(provider_collection.default).to be_nil
123
+ end
124
+
125
+ it 'raises an ArgumentError for any other provider' do
126
+ expect do
127
+ provider_collection.default = other_provider
128
+ end.to raise_error ArgumentError
129
+ expect(provider_collection.default).to be_nil
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#resolve' do
135
+ context 'when the file extension is one of the providers' do
136
+ it 'returns the corresponding provider' do
137
+ expect(provider_collection.resolve 'hola.one').to eq first_provider
138
+ expect(provider_collection.resolve 'hola.two').to eq second_provider
139
+ end
140
+ end
141
+
142
+ context 'when the file extension is not one of the providers' do
143
+ it 'returns the default provider' do
144
+ expect(provider_collection.resolve 'hola.unknown').to eq first_provider
145
+ expect(provider_collection.resolve 'hola').to eq first_provider
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#reset' do
151
+ let(:configured_default) { second_provider }
152
+ let(:provider) { double :provider }
153
+
154
+ before do
155
+ provider_collection.add :three, provider
156
+ provider_collection.default = provider
157
+ end
158
+
159
+ it 'resets to back to the initially configured values' do
160
+ provider_collection.reset
161
+ expect(provider_collection[:three]).to be_nil
162
+ expect(provider_collection.default).to eq second_provider
163
+ end
164
+ end
165
+ end