rambling-trie 1.0.2 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +6 -3
  3. data/Guardfile +3 -1
  4. data/README.md +30 -12
  5. data/Rakefile +8 -0
  6. data/lib/rambling-trie.rb +2 -0
  7. data/lib/rambling/trie.rb +48 -26
  8. data/lib/rambling/trie/comparable.rb +6 -3
  9. data/lib/rambling/trie/compressible.rb +16 -0
  10. data/lib/rambling/trie/compressor.rb +39 -24
  11. data/lib/rambling/trie/configuration.rb +3 -1
  12. data/lib/rambling/trie/configuration/properties.rb +18 -9
  13. data/lib/rambling/trie/configuration/provider_collection.rb +38 -17
  14. data/lib/rambling/trie/container.rb +123 -36
  15. data/lib/rambling/trie/enumerable.rb +6 -4
  16. data/lib/rambling/trie/inspectable.rb +2 -0
  17. data/lib/rambling/trie/invalid_operation.rb +3 -1
  18. data/lib/rambling/trie/nodes.rb +13 -0
  19. data/lib/rambling/trie/nodes/compressed.rb +98 -0
  20. data/lib/rambling/trie/nodes/missing.rb +12 -0
  21. data/lib/rambling/trie/nodes/node.rb +183 -0
  22. data/lib/rambling/trie/nodes/raw.rb +82 -0
  23. data/lib/rambling/trie/readers.rb +3 -1
  24. data/lib/rambling/trie/readers/plain_text.rb +3 -11
  25. data/lib/rambling/trie/serializers.rb +3 -1
  26. data/lib/rambling/trie/serializers/file.rb +2 -0
  27. data/lib/rambling/trie/serializers/marshal.rb +15 -5
  28. data/lib/rambling/trie/serializers/yaml.rb +21 -5
  29. data/lib/rambling/trie/serializers/zip.rb +15 -8
  30. data/lib/rambling/trie/stringifyable.rb +8 -2
  31. data/lib/rambling/trie/version.rb +3 -1
  32. data/rambling-trie.gemspec +21 -10
  33. data/spec/assets/test_words.es_DO.txt +1 -0
  34. data/spec/integration/rambling/trie_spec.rb +44 -35
  35. data/spec/lib/rambling/trie/comparable_spec.rb +8 -15
  36. data/spec/lib/rambling/trie/compressor_spec.rb +90 -13
  37. data/spec/lib/rambling/trie/configuration/properties_spec.rb +21 -13
  38. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +18 -34
  39. data/spec/lib/rambling/trie/container_spec.rb +183 -217
  40. data/spec/lib/rambling/trie/enumerable_spec.rb +14 -9
  41. data/spec/lib/rambling/trie/inspectable_spec.rb +36 -11
  42. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
  43. data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
  44. data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
  45. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +3 -1
  46. data/spec/lib/rambling/trie/serializers/file_spec.rb +6 -4
  47. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +5 -7
  48. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +5 -7
  49. data/spec/lib/rambling/trie/serializers/zip_spec.rb +18 -20
  50. data/spec/lib/rambling/trie/stringifyable_spec.rb +14 -11
  51. data/spec/lib/rambling/trie_spec.rb +18 -11
  52. data/spec/spec_helper.rb +10 -5
  53. data/spec/support/config.rb +10 -0
  54. data/spec/support/helpers/add_word.rb +20 -0
  55. data/spec/support/helpers/one_line_heredoc.rb +11 -0
  56. data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
  57. data/spec/support/shared_examples/a_serializable_trie.rb +10 -6
  58. data/spec/support/shared_examples/a_serializer.rb +9 -1
  59. data/spec/support/shared_examples/a_trie_data_structure.rb +2 -0
  60. data/spec/support/shared_examples/a_trie_node.rb +127 -0
  61. data/spec/{lib/rambling/trie/compressed_node_spec.rb → support/shared_examples/a_trie_node_implementation.rb} +25 -72
  62. metadata +42 -31
  63. data/lib/rambling/trie/compressable.rb +0 -14
  64. data/lib/rambling/trie/compressed_node.rb +0 -120
  65. data/lib/rambling/trie/missing_node.rb +0 -8
  66. data/lib/rambling/trie/node.rb +0 -97
  67. data/lib/rambling/trie/raw_node.rb +0 -96
  68. data/spec/lib/rambling/trie/node_spec.rb +0 -86
  69. data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
  70. data/spec/support/shared_examples/a_compressable_trie.rb +0 -26
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rambling
2
4
  module Trie
3
5
  # Provides the String representation behavior for the trie data structure.
@@ -6,14 +8,18 @@ module Rambling
6
8
  # @return [String] the string representation of the current node.
7
9
  # @raise [InvalidOperation] if node is not terminal or is root.
8
10
  def as_word
9
- raise Rambling::Trie::InvalidOperation, 'Cannot represent branch as a word' if letter && !terminal?
11
+ if letter && !terminal?
12
+ raise Rambling::Trie::InvalidOperation,
13
+ 'Cannot represent branch as a word'
14
+ end
15
+
10
16
  to_s
11
17
  end
12
18
 
13
19
  # String representation of the current node.
14
20
  # @return [String] the string representation of the current node.
15
21
  def to_s
16
- parent.to_s << letter.to_s
22
+ parent.to_s + letter.to_s
17
23
  end
18
24
  end
19
25
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rambling
2
4
  module Trie
3
5
  # Current version of the rambling-trie.
4
- VERSION = '1.0.2'.freeze
6
+ VERSION = '2.2.0'
5
7
  end
6
8
  end
@@ -1,26 +1,37 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
4
  require 'rambling/trie/version'
4
5
 
5
6
  Gem::Specification.new do |gem|
6
7
  gem.authors = ['Edgar Gonzalez', 'Lilibeth De La Cruz']
7
8
  gem.email = ['edggonzalezg@gmail.com', 'lilibethdlc@gmail.com']
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
+
10
+ gem.description = <<~DESCRIPTION.gsub(%r{\s+}, ' ')
11
+ The Rambling Trie is a Ruby implementation of the trie data structure, which
12
+ includes compression abilities and is designed to be very fast to traverse.
13
+ DESCRIPTION
14
+
9
15
  gem.summary = 'A Ruby implementation of the trie data structure.'
10
16
  gem.homepage = 'http://github.com/gonzedge/rambling-trie'
11
17
  gem.date = Time.now.strftime '%Y-%m-%d'
12
18
 
13
- gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename f }
14
- gem.files = `git ls-files -- {lib,*file,*.gemspec,LICENSE*,README*}`.split "\n"
15
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split "\n"
16
- gem.require_paths = ['lib']
19
+ executables = `git ls-files -- bin/*`.split "\n"
20
+ files = `git ls-files -- {lib,*file,*.gemspec,LICENSE*,README*}`.split "\n"
21
+ test_files = `git ls-files -- {test,spec,features}/*`.split "\n"
22
+
23
+ gem.executables = executables.map { |f| File.basename f }
24
+ gem.files = files
25
+ gem.test_files = test_files
26
+ gem.require_paths = %w(lib)
17
27
 
18
28
  gem.name = 'rambling-trie'
19
29
  gem.license = 'MIT'
20
30
  gem.version = Rambling::Trie::VERSION
21
31
  gem.platform = Gem::Platform::RUBY
32
+ gem.required_ruby_version = '>= 2.5', '<= 3.0.0'
22
33
 
23
- gem.add_development_dependency 'rspec', '~> 3.5'
24
- gem.add_development_dependency 'rake', '~> 12.0'
25
- gem.add_development_dependency 'yard', '~> 0.9.5'
34
+ gem.add_development_dependency 'rake', '~> 13.0'
35
+ gem.add_development_dependency 'rspec', '~> 3.9'
36
+ gem.add_development_dependency 'yard', '~> 0.9.25'
26
37
  end
@@ -21,3 +21,4 @@ tus
21
21
  tuyos
22
22
  verdad
23
23
  verdadero
24
+ 🙃
@@ -1,61 +1,72 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
4
+ require 'zip'
2
5
 
3
6
  describe Rambling::Trie do
4
- describe 'with words provided directly' do
5
- it_behaves_like 'a compressable trie' do
6
- let(:words) { %w[a couple of words for our full trie integration test] }
7
- let(:trie) { Rambling::Trie.create { |t| words.each { |w| t << w } } }
7
+ let(:assets_path) { File.join ::SPEC_ROOT, 'assets' }
8
+
9
+ context 'when providing words directly' do
10
+ it_behaves_like 'a compressible trie' do
11
+ let(:trie) { Rambling::Trie.create }
12
+ let(:words) { %w(a couple of words for our full trie integration test) }
13
+
14
+ before do
15
+ trie.concat words
16
+ end
8
17
  end
9
18
  end
10
19
 
11
- describe 'with words from a file' do
12
- it_behaves_like 'a compressable trie' do
13
- let(:filepath) { File.join ::SPEC_ROOT, 'assets', 'test_words.en_US.txt' }
14
- let(:words) { File.readlines(filepath).map &:chomp! }
15
- let(:trie) { Rambling::Trie.create filepath }
20
+ context 'when provided with words with unicode characters' do
21
+ it_behaves_like 'a compressible trie' do
22
+ let(:trie) { Rambling::Trie.create }
23
+ let(:words) do
24
+ %w(poquísimas palabras para nuestra prueba de integración completa 🙃)
25
+ end
26
+
27
+ before do
28
+ trie.concat words
29
+ end
16
30
  end
17
31
  end
18
32
 
19
- describe 'with words with unicode characters' do
20
- it_behaves_like 'a compressable trie' do
21
- let(:words) { %w[poquísimas palabras para nuestra prueba de integración completa] }
22
- let(:trie) { Rambling::Trie.create { |t| words.each { |w| t << w } } }
33
+ context 'when provided with a filepath' do
34
+ let(:trie) { Rambling::Trie.create filepath }
35
+ let(:words) { File.readlines(filepath).map(&:chomp) }
36
+
37
+ context 'with english words' do
38
+ it_behaves_like 'a compressible trie' do
39
+ let(:filepath) { File.join assets_path, 'test_words.en_US.txt' }
40
+ end
23
41
  end
24
- end
25
42
 
26
- describe 'with words with unicode characters from a file' do
27
- it_behaves_like 'a compressable trie' do
28
- let(:filepath) { File.join ::SPEC_ROOT, 'assets', 'test_words.en_US.txt' }
29
- let(:words) { File.readlines(filepath).map &:chomp! }
30
- let(:trie) { Rambling::Trie.create filepath }
43
+ context 'with unicode characters' do
44
+ it_behaves_like 'a compressible trie' do
45
+ let(:filepath) { File.join assets_path, 'test_words.es_DO.txt' }
46
+ end
31
47
  end
32
48
  end
33
49
 
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' }
50
+ describe 'dump and load' do
51
+ let(:words_filepath) { File.join assets_path, 'test_words.en_US.txt' }
52
+ let(:words) { File.readlines(words_filepath).map(&:chomp) }
39
53
 
40
54
  context 'when serialized with Ruby marshal format (default)' do
41
55
  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 }
56
+ let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
57
+ let(:format) { :marshal }
45
58
  end
46
59
  end
47
60
 
48
61
  context 'when serialized with YAML' do
49
62
  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 }
63
+ let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
64
+ let(:format) { :yml }
53
65
  end
54
66
  end
55
67
 
56
68
  context 'when serialized with zipped Ruby marshal format' do
57
69
  before do
58
- require 'zip'
59
70
  @original_on_exists_proc = ::Zip.on_exists_proc
60
71
  @original_continue_on_exists_proc = ::Zip.continue_on_exists_proc
61
72
  ::Zip.on_exists_proc = true
@@ -63,15 +74,13 @@ describe Rambling::Trie do
63
74
  end
64
75
 
65
76
  after do
66
- require 'zip'
67
77
  ::Zip.on_exists_proc = @original_on_exists_proc
68
78
  ::Zip.continue_on_exists_proc = @original_continue_on_exists_proc
69
79
  end
70
80
 
71
81
  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 }
82
+ let(:trie_to_serialize) { Rambling::Trie.create words_filepath }
83
+ let(:format) { 'marshal.zip' }
75
84
  end
76
85
  end
77
86
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Comparable do
4
6
  describe '#==' do
5
- let(:node_1) { Rambling::Trie::RawNode.new }
6
- let(:node_2) { Rambling::Trie::RawNode.new }
7
+ let(:node_1) { Rambling::Trie::Nodes::Raw.new }
8
+ let(:node_2) { Rambling::Trie::Nodes::Raw.new }
7
9
 
8
10
  context 'when the nodes do not have the same letter' do
9
11
  before do
@@ -67,37 +69,28 @@ describe Rambling::Trie::Comparable do
67
69
  context 'when the nodes have the same letter and the same children' do
68
70
  before do
69
71
  node_1.letter = :t
70
- node_1.add 'hese'
71
- node_1.add 'hree'
72
- node_1.add 'hings'
72
+ add_words node_1, %w(hese hree hings)
73
73
 
74
74
  node_2.letter = :t
75
- node_2.add 'hese'
76
- node_2.add 'hree'
77
- node_2.add 'hings'
75
+ add_words node_2, %w(hese hree hings)
78
76
  end
79
77
 
80
78
  it 'returns true' do
81
79
  expect(node_1).to eq node_2
82
- expect(node_1[:h][:e][:s][:e]).to eq node_2[:h][:e][:s][:e]
83
80
  end
84
81
  end
85
82
 
86
83
  context 'when the nodes have the same letter but different children' do
87
84
  before do
88
85
  node_1.letter = :t
89
- node_1.add 'hese'
90
- node_1.add 'wo'
86
+ add_words node_1, %w(hese wo)
91
87
 
92
88
  node_2.letter = :t
93
- node_2.add 'hese'
94
- node_2.add 'hree'
95
- node_2.add 'hings'
89
+ add_words node_2, %w(hese hree hings)
96
90
  end
97
91
 
98
92
  it 'returns false' do
99
93
  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
94
  end
102
95
  end
103
96
  end
@@ -1,31 +1,108 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Compressor do
4
6
  let(:compressor) { Rambling::Trie::Compressor.new }
5
7
 
6
8
  describe '#compress' do
7
- let(:words) { %w(a few words hello hell) }
8
- let(:root) do
9
- Rambling::Trie::RawNode.new
9
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
10
+
11
+ it 'compresses the node' do
12
+ add_words node, %w(a few words hello hell)
13
+ compressed = compressor.compress node
14
+
15
+ expect(compressed.children_tree.keys).to eq %i(a f w h)
16
+ end
17
+
18
+ context 'with at least one word' do
19
+ before do
20
+ add_words node, %w(all the words)
21
+ end
22
+
23
+ it 'keeps the node letter nil' do
24
+ compressed = compressor.compress node
25
+
26
+ expect(compressed.letter).to be_nil
27
+ end
10
28
  end
11
29
 
12
- before do
13
- words.each { |w| root.add w.clone }
30
+ context 'with a single word' do
31
+ before do
32
+ add_word node, 'all'
33
+ end
34
+
35
+ it 'compresses into a single node without children' do
36
+ compressed = compressor.compress node
37
+
38
+ expect(compressed[:a].letter).to eq :all
39
+ expect(compressed[:a].children.size).to eq 0
40
+ expect(compressed[:a]).to be_terminal
41
+ expect(compressed[:a]).to be_compressed
42
+ end
14
43
  end
15
44
 
16
- it 'generates a new root with the words from the passed root' do
17
- new_root = compressor.compress root
45
+ context 'with two words' do
46
+ before do
47
+ add_words node, %w(all ask)
48
+ end
49
+
50
+ it 'compresses into corresponding three nodes' do
51
+ compressed = compressor.compress node
52
+
53
+ expect(compressed[:a].letter).to eq :a
54
+ expect(compressed[:a].children.size).to eq 2
55
+
56
+ expect(compressed[:a][:l].letter).to eq :ll
57
+ expect(compressed[:a][:s].letter).to eq :sk
58
+
59
+ expect(compressed[:a][:l].children.size).to eq 0
60
+ expect(compressed[:a][:s].children.size).to eq 0
18
61
 
19
- expect(words).not_to be_empty
20
- words.each do |word|
21
- expect(new_root).to include word
62
+ expect(compressed[:a][:l]).to be_terminal
63
+ expect(compressed[:a][:s]).to be_terminal
64
+
65
+ expect(compressed[:a][:l]).to be_compressed
66
+ expect(compressed[:a][:s]).to be_compressed
22
67
  end
23
68
  end
24
69
 
25
- it 'compresses the new root' do
26
- new_root = compressor.compress root
70
+ it 'reassigns the parent nodes correctly' do
71
+ add_words node, %w(repay rest repaint)
72
+ compressed = compressor.compress node
73
+
74
+ expect(compressed[:r].letter).to eq :re
75
+ expect(compressed[:r].parent).to eq compressed
76
+ expect(compressed[:r].children.size).to eq 2
77
+
78
+ expect(compressed[:r][:p].letter).to eq :pa
79
+ expect(compressed[:r][:p].parent).to eq compressed[:r]
80
+ expect(compressed[:r][:p].children.size).to eq 2
81
+
82
+ expect(compressed[:r][:s].letter).to eq :st
83
+ expect(compressed[:r][:s].parent).to eq compressed[:r]
84
+ expect(compressed[:r][:s].children.size).to eq 0
85
+
86
+ expect(compressed[:r][:p][:y].letter).to eq :y
87
+ expect(compressed[:r][:p][:y].parent).to eq compressed[:r][:p]
88
+ expect(compressed[:r][:p][:y].children.size).to eq 0
89
+
90
+ expect(compressed[:r][:p][:i].letter).to eq :int
91
+ expect(compressed[:r][:p][:i].parent).to eq compressed[:r][:p]
92
+ expect(compressed[:r][:p][:i].children.size).to eq 0
93
+ end
94
+
95
+ it 'does not compress terminal nodes' do
96
+ add_words node, %w(you your yours)
97
+ compressed = compressor.compress node
98
+
99
+ expect(compressed[:y].letter).to eq :you
100
+
101
+ expect(compressed[:y][:r].letter).to eq :r
102
+ expect(compressed[:y][:r]).to be_compressed
27
103
 
28
- expect(new_root.children_tree.keys).to eq %i(a few words hell)
104
+ expect(compressed[:y][:r][:s].letter).to eq :s
105
+ expect(compressed[:y][:r][:s]).to be_compressed
29
106
  end
30
107
  end
31
108
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Rambling::Trie::Configuration::Properties do
@@ -6,27 +8,33 @@ describe Rambling::Trie::Configuration::Properties do
6
8
  describe '.new' do
7
9
  it 'configures the serializers' do
8
10
  serializers = properties.serializers
9
- expect(serializers.keys).to match_array %i(marshal yaml yml zip)
10
11
 
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
12
+ expect(serializers.formats).to match_array %i(marshal yaml yml zip)
13
+ expect(serializers.providers.to_a).to match_array [
14
+ [:marshal, Rambling::Trie::Serializers::Marshal],
15
+ [:yaml, Rambling::Trie::Serializers::Yaml],
16
+ [:yml, Rambling::Trie::Serializers::Yaml],
17
+ [:zip, Rambling::Trie::Serializers::Zip],
18
+ ]
15
19
  end
16
20
 
17
21
  it 'configures the readers' do
18
22
  readers = properties.readers
19
- expect(readers.keys).to match_array %i(txt)
20
23
 
21
- expect(readers[:txt]).to be_instance_of Rambling::Trie::Readers::PlainText
24
+ expect(readers.formats).to match_array %i(txt)
25
+ expect(readers.providers.to_a).to match_array [
26
+ [:txt, Rambling::Trie::Readers::PlainText],
27
+ ]
22
28
  end
23
29
 
24
30
  it 'configures the compressor' do
25
- expect(properties.compressor).to be_instance_of Rambling::Trie::Compressor
31
+ compressor = properties.compressor
32
+ expect(compressor).to be_instance_of Rambling::Trie::Compressor
26
33
  end
27
34
 
28
35
  it 'configures the root_builder' do
29
- expect(properties.root_builder.call).to be_instance_of Rambling::Trie::RawNode
36
+ root = properties.root_builder.call
37
+ expect(root).to be_instance_of Rambling::Trie::Nodes::Raw
30
38
  end
31
39
  end
32
40
 
@@ -37,13 +45,13 @@ describe Rambling::Trie::Configuration::Properties do
37
45
  end
38
46
 
39
47
  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
48
+ expect(properties.serializers.formats).to include :test
49
+ expect(properties.readers.formats).to include :test
42
50
 
43
51
  properties.reset
44
52
 
45
- expect(properties.serializers.keys).not_to include :test
46
- expect(properties.readers.keys).not_to include :test
53
+ expect(properties.serializers.formats).not_to include :test
54
+ expect(properties.readers.formats).not_to include :test
47
55
  end
48
56
  end
49
57
  end