rambling-trie-opal 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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/Guardfile +10 -0
  4. data/LICENSE +26 -0
  5. data/README.md +301 -0
  6. data/Rakefile +15 -0
  7. data/lib/rambling-trie.rb +3 -0
  8. data/lib/rambling/trie.rb +119 -0
  9. data/lib/rambling/trie/comparable.rb +19 -0
  10. data/lib/rambling/trie/compressible.rb +16 -0
  11. data/lib/rambling/trie/compressor.rb +64 -0
  12. data/lib/rambling/trie/configuration.rb +16 -0
  13. data/lib/rambling/trie/configuration/properties.rb +75 -0
  14. data/lib/rambling/trie/configuration/provider_collection.rb +122 -0
  15. data/lib/rambling/trie/container.rb +226 -0
  16. data/lib/rambling/trie/enumerable.rb +29 -0
  17. data/lib/rambling/trie/inspectable.rb +39 -0
  18. data/lib/rambling/trie/invalid_operation.rb +15 -0
  19. data/lib/rambling/trie/nodes.rb +18 -0
  20. data/lib/rambling/trie/nodes/compressed.rb +98 -0
  21. data/lib/rambling/trie/nodes/missing.rb +12 -0
  22. data/lib/rambling/trie/nodes/node.rb +183 -0
  23. data/lib/rambling/trie/nodes/raw.rb +82 -0
  24. data/lib/rambling/trie/readers.rb +15 -0
  25. data/lib/rambling/trie/readers/plain_text.rb +18 -0
  26. data/lib/rambling/trie/serializers.rb +18 -0
  27. data/lib/rambling/trie/serializers/file.rb +27 -0
  28. data/lib/rambling/trie/serializers/marshal.rb +48 -0
  29. data/lib/rambling/trie/serializers/yaml.rb +55 -0
  30. data/lib/rambling/trie/serializers/zip.rb +74 -0
  31. data/lib/rambling/trie/stringifyable.rb +26 -0
  32. data/lib/rambling/trie/version.rb +8 -0
  33. data/rambling-trie-opal.gemspec +36 -0
  34. data/spec/assets/test_words.en_US.txt +23 -0
  35. data/spec/assets/test_words.es_DO.txt +24 -0
  36. data/spec/integration/rambling/trie_spec.rb +87 -0
  37. data/spec/lib/rambling/trie/comparable_spec.rb +97 -0
  38. data/spec/lib/rambling/trie/compressor_spec.rb +108 -0
  39. data/spec/lib/rambling/trie/configuration/properties_spec.rb +57 -0
  40. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +149 -0
  41. data/spec/lib/rambling/trie/container_spec.rb +591 -0
  42. data/spec/lib/rambling/trie/enumerable_spec.rb +42 -0
  43. data/spec/lib/rambling/trie/inspectable_spec.rb +56 -0
  44. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
  45. data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
  46. data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
  47. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +16 -0
  48. data/spec/lib/rambling/trie/serializers/file_spec.rb +13 -0
  49. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +12 -0
  50. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +12 -0
  51. data/spec/lib/rambling/trie/serializers/zip_spec.rb +28 -0
  52. data/spec/lib/rambling/trie/stringifyable_spec.rb +85 -0
  53. data/spec/lib/rambling/trie_spec.rb +182 -0
  54. data/spec/spec_helper.rb +37 -0
  55. data/spec/support/config.rb +15 -0
  56. data/spec/support/helpers/add_word.rb +20 -0
  57. data/spec/support/helpers/one_line_heredoc.rb +11 -0
  58. data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
  59. data/spec/support/shared_examples/a_serializable_trie.rb +30 -0
  60. data/spec/support/shared_examples/a_serializer.rb +37 -0
  61. data/spec/support/shared_examples/a_trie_data_structure.rb +31 -0
  62. data/spec/support/shared_examples/a_trie_node.rb +127 -0
  63. data/spec/support/shared_examples/a_trie_node_implementation.rb +152 -0
  64. data/spec/tmp/.gitkeep +0 -0
  65. metadata +179 -0
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ module Rambling
6
+ module Trie
7
+ describe Enumerable do
8
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
9
+ let(:words) { %w(add some words and another word) }
10
+
11
+ before do
12
+ add_words node, words
13
+ end
14
+
15
+ describe '#each' do
16
+ it 'returns an enumerator' do
17
+ expect(node.each).to be_a Enumerator
18
+ end
19
+
20
+ it 'includes every word contained in the trie' do
21
+ node.each do |word|
22
+ expect(words).to include word
23
+ end
24
+
25
+ expect(node.count).to eq words.count
26
+ end
27
+ end
28
+
29
+ describe '#size' do
30
+ it 'delegates to #count' do
31
+ expect(node.size).to eq words.size
32
+ end
33
+ end
34
+
35
+ it 'includes the core Enumerable module' do
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
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Inspectable do
6
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
7
+
8
+ before do
9
+ add_words node, %w(only three words)
10
+ end
11
+
12
+ describe '#inspect' do
13
+ let(:child) { node[:o] }
14
+ let(:terminal_node) { node[:o][:n][:l][:y] }
15
+
16
+ it 'returns a pretty printed version of the node' do
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
34
+ end
35
+
36
+ context 'for a compressed node' do
37
+ let(:compressor) { Rambling::Trie::Compressor.new }
38
+ let(:compressed) { compressor.compress node }
39
+ let(:compressed_child) { compressed[:o] }
40
+
41
+ it 'returns a pretty printed version of the compressed node' do
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
53
+ end
54
+ end
55
+ end
56
+ 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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Readers::PlainText do
6
+ describe '#each_word' do
7
+ let(:filepath) { File.join(::SPEC_ROOT, 'assets', 'test_words.en_US.txt') }
8
+ let(:words) { File.readlines(filepath).map(&:chomp) }
9
+
10
+ it 'yields every word yielded by the file' do
11
+ yielded = []
12
+ subject.each_word(filepath) { |word| yielded << word }
13
+ expect(yielded).to eq words
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Serializers::File do
6
+ it_behaves_like 'a serializer' do
7
+ let(:serializer) { Rambling::Trie::Serializers::File.new }
8
+ let(:format) { :file }
9
+
10
+ let(:content) { trie.to_a.join ' ' }
11
+ let(:formatted_content) { content }
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Serializers::Marshal do
6
+ it_behaves_like 'a serializer' do
7
+ let(:serializer) { Rambling::Trie::Serializers::Marshal.new }
8
+ let(:format) { :marshal }
9
+
10
+ let(:formatted_content) { Marshal.dump content }
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Serializers::Yaml do
6
+ it_behaves_like 'a serializer' do
7
+ let(:serializer) { Rambling::Trie::Serializers::Yaml.new }
8
+ let(:format) { :yml }
9
+
10
+ let(:formatted_content) { YAML.dump content }
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
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' }
10
+
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 }
17
+
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
+ # end
27
+ # end
28
+ # end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Stringifyable do
6
+ describe '#as_word' do
7
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
8
+
9
+ context 'for an empty node' do
10
+ before do
11
+ add_word node, ''
12
+ end
13
+
14
+ it 'returns nil' do
15
+ expect(node.as_word).to be_empty
16
+ end
17
+ end
18
+
19
+ context 'for one letter' do
20
+ before do
21
+ node.letter = :a
22
+ add_word node, ''
23
+ end
24
+
25
+ it 'returns the expected one letter word' do
26
+ expect(node.as_word).to eq 'a'
27
+ end
28
+ end
29
+
30
+ context 'for a small word' do
31
+ before do
32
+ node.letter = :a
33
+ add_word node, 'll'
34
+ end
35
+
36
+ it 'returns the expected small word' do
37
+ expect(node[:l][:l].as_word).to eq 'all'
38
+ end
39
+
40
+ it 'raises an error for a non terminal node' do
41
+ expect { node[:l].as_word }
42
+ .to raise_error Rambling::Trie::InvalidOperation
43
+ end
44
+ end
45
+
46
+ context 'for a long word' do
47
+ before do
48
+ node.letter = :b
49
+ add_word node, 'eautiful'
50
+ end
51
+
52
+ it 'returns the expected long word' do
53
+ expect(node[:e][:a][:u][:t][:i][:f][:u][:l].as_word).to eq 'beautiful'
54
+ end
55
+ end
56
+
57
+ context 'for a node with nil letter' do
58
+ let(:node) { Rambling::Trie::Nodes::Raw.new nil }
59
+
60
+ it 'returns nil' do
61
+ expect(node.as_word).to be_empty
62
+ end
63
+ end
64
+
65
+ context 'for a compressed node' do
66
+ let(:compressor) { Rambling::Trie::Compressor.new }
67
+ let(:compressed_node) { compressor.compress node }
68
+
69
+ before do
70
+ node.letter = :a
71
+ add_words node, %w(m dd)
72
+ end
73
+
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'
77
+ end
78
+
79
+ it 'raise an error for non terminal nodes' do
80
+ expect { compressed_node.as_word }
81
+ .to raise_error Rambling::Trie::InvalidOperation
82
+ end
83
+ end
84
+ end
85
+ end