rambling-trie 2.5.1 → 2.7.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +2 -1
  3. data/Gemfile +2 -0
  4. data/README.md +28 -20
  5. data/Steepfile +5 -1
  6. data/lib/rambling/trie/comparable.rb +4 -2
  7. data/lib/rambling/trie/compressible.rb +1 -1
  8. data/lib/rambling/trie/compressor.rb +16 -4
  9. data/lib/rambling/trie/configuration/properties.rb +3 -1
  10. data/lib/rambling/trie/configuration/provider_collection.rb +7 -7
  11. data/lib/rambling/trie/container.rb +46 -21
  12. data/lib/rambling/trie/enumerable.rb +8 -3
  13. data/lib/rambling/trie/inspectable.rb +6 -1
  14. data/lib/rambling/trie/nodes/compressed.rb +27 -19
  15. data/lib/rambling/trie/nodes/missing.rb +17 -0
  16. data/lib/rambling/trie/nodes/node.rb +28 -21
  17. data/lib/rambling/trie/nodes/raw.rb +11 -8
  18. data/lib/rambling/trie/not_implemented.rb +23 -0
  19. data/lib/rambling/trie/readers/plain_text.rb +4 -1
  20. data/lib/rambling/trie/readers/reader.rb +5 -2
  21. data/lib/rambling/trie/serializers/marshal.rb +5 -0
  22. data/lib/rambling/trie/serializers/serializer.rb +8 -4
  23. data/lib/rambling/trie/serializers/yaml.rb +5 -0
  24. data/lib/rambling/trie/serializers/zip.rb +31 -21
  25. data/lib/rambling/trie/stringifyable.rb +2 -5
  26. data/lib/rambling/trie/version.rb +1 -1
  27. data/lib/rambling/trie.rb +14 -10
  28. data/rambling-trie.gemspec +1 -1
  29. data/sig/lib/nilable.rbs +3 -0
  30. data/sig/lib/rambling/trie/comparable.rbs +6 -4
  31. data/sig/lib/rambling/trie/compressible.rbs +2 -2
  32. data/sig/lib/rambling/trie/compressor.rbs +6 -6
  33. data/sig/lib/rambling/trie/configuration/properties.rbs +5 -5
  34. data/sig/lib/rambling/trie/configuration/provider_collection.rbs +0 -4
  35. data/sig/lib/rambling/trie/container.rbs +24 -23
  36. data/sig/lib/rambling/trie/enumerable.rbs +6 -5
  37. data/sig/lib/rambling/trie/inspectable.rbs +8 -3
  38. data/sig/lib/rambling/trie/nodes/compressed.rbs +6 -4
  39. data/sig/lib/rambling/trie/nodes/missing.rbs +10 -2
  40. data/sig/lib/rambling/trie/nodes/node.rbs +21 -19
  41. data/sig/lib/rambling/trie/nodes/raw.rbs +5 -5
  42. data/sig/lib/rambling/trie/not_implemented.rbs +10 -0
  43. data/sig/lib/rambling/trie/readers/plain_text.rbs +2 -2
  44. data/sig/lib/rambling/trie/readers/reader.rbs +2 -0
  45. data/sig/lib/rambling/trie/serializers/marshal.rbs +1 -1
  46. data/sig/lib/rambling/trie/serializers/serializer.rbs +2 -0
  47. data/sig/lib/rambling/trie/serializers/yaml.rbs +1 -1
  48. data/sig/lib/rambling/trie/serializers/zip.rbs +8 -6
  49. data/sig/lib/rambling/trie/stringifyable.rbs +4 -4
  50. data/sig/lib/rambling/trie.rbs +10 -10
  51. metadata +8 -10
  52. data/sig/lib/zip/entry.rbs +0 -11
  53. data/sig/lib/zip/file.rbs +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c38f152f6ffdb5b7c3f5abde873ae3024651a52a7697fa4e706d01e28fe504e
4
- data.tar.gz: 8cdec337b99995019a24d702b48dce2ee3e9a9e6acde298cd5cfef8086e93bc9
3
+ metadata.gz: 3a9d76d13a38a54c80ca90236346dcfe280fea62d46d8e9318efc8f2653b6f40
4
+ data.tar.gz: 0ab262b5f456d0e299fc16614bb0c7103406899b0a88dce3ef69d0d1f403fd3c
5
5
  SHA512:
6
- metadata.gz: 6bbadda2f93e7efdae19f0db8749e304be56b288d62095a269904c4d6e3b6e2f7723acba618fd4e8e8de416108ca9e6249c7a6c9ce5a70e70036e6ad287cb7e2
7
- data.tar.gz: b60a49628ba61bca14d5a92573df83203feb0346d306adbc628ab7dfad54da82574902ee1f4985a365582a6c5a285b788a108ee7e7d2c22b0ce3f06dc5eccf78
6
+ metadata.gz: 13ccb7f63311ae91544442fe75e1620ecb91ef87501150fa46f454f80b7b4d51cda69ebeb8c82e474173db815e5f536b9cf03cccb3fa8e6233facfbd7f265949
7
+ data.tar.gz: dfab4eaea2613f6ad5957579089056441d3f01533112fff081504da2be2e18f77267350f6cedd4c61d65d88075cf0e83d22e5124370fac45fa2dadc36b22ddcb
data/Dockerfile CHANGED
@@ -1,4 +1,5 @@
1
- FROM ruby:3.3.6
1
+ ARG RUBY_VERSION=3.3.6
2
+ FROM ruby:${RUBY_VERSION}
2
3
 
3
4
  RUN bundle config --global frozen 1
4
5
 
data/Gemfile CHANGED
@@ -18,6 +18,7 @@ group :test do
18
18
  end
19
19
 
20
20
  group :local do
21
+ gem 'benchmark', require: false
21
22
  gem 'benchmark-ips', require: false
22
23
  gem 'flamegraph', require: false
23
24
  gem 'flog', require: false
@@ -26,6 +27,7 @@ group :local do
26
27
  gem 'guard-rspec', require: false
27
28
  gem 'guard-rubocop', require: false
28
29
  gem 'guard-yard', require: false
30
+ gem 'irb', require: false
29
31
  gem 'mdl', require: false
30
32
  gem 'memory_profiler', require: false
31
33
  gem 'pry', require: false
data/README.md CHANGED
@@ -9,9 +9,6 @@
9
9
  [![Documentation Status][inch_ci_badge]][rubydoc]
10
10
  [![CodeQL Status][github_action_codeql_badge]][github_action_codeql_link]
11
11
 
12
- [![Code Climate Grade][code_climate_grade_badge]][code_climate_link]
13
- [![Code Climate Issue Count][code_climate_issues_badge]][code_climate_link]
14
-
15
12
  The Rambling Trie is a Ruby implementation of the [trie data structure][trie_wiki], which includes compression abilities
16
13
  and is designed to be very fast to traverse.
17
14
 
@@ -21,10 +18,10 @@ and is designed to be very fast to traverse.
21
18
 
22
19
  You will need:
23
20
 
24
- * Ruby 3.1.0 or up
21
+ * Ruby 3.2.0 or up
25
22
  * RubyGems
26
23
 
27
- See [RVM][rvm], [rbenv][rbenv] or [chruby][chruby] for more information on how to manage Ruby versions.
24
+ See [asdf][asdf], [RVM][rvm], [rbenv][rbenv] or [chruby][chruby] for more information on how to manage Ruby versions.
28
25
 
29
26
  ### Installation
30
27
 
@@ -77,9 +74,8 @@ trie
77
74
  ```
78
75
 
79
76
  If you want to use a custom file format, you will need to provide a custom `Reader` that defines an `#each_word` method
80
- that yields each word contained in the file. Look at the [`PlainText` reader][rambling_trie_plain_text_reader] class for
81
- an example, and at the [Configuration section][rambling_trie_configuration] to see how to add your own custom file
82
- readers.
77
+ that yields each word contained in the file. Look at the [`PlainText` reader](./lib/rambling/trie/readers/plain_text.rb)
78
+ class for an example, and at the [Configuration section](#configuration) to see how to add your own custom file readers.
83
79
 
84
80
  ### Operations
85
81
 
@@ -182,7 +178,7 @@ trie.all? { |word| word.include? 'x' }
182
178
 
183
179
  ### Serialization
184
180
 
185
- Starting from version 1.0.0, you can store a full trie instance on disk and retrieve/use it later on. Loading a trie
181
+ Starting from version `1.0.0`, you can store a full trie instance on disk and retrieve/use it later on. Loading a trie
186
182
  from disk takes less time, less cpu and less memory than loading every word into the trie every time. This is
187
183
  particularly useful for production applications, when you have word lists that you know are going to be static, or that
188
184
  change with little frequency.
@@ -206,22 +202,29 @@ Currently, these formats are supported to store tries on disk:
206
202
 
207
203
  * Ruby's [binary (Marshal)][marshal] format
208
204
  * [YAML][yaml]
205
+ * Zip version of any other format ([optional `rubyzip` dependency](#optional-rubyzip-dependency))
206
+
207
+ > When dumping into or loading from disk, the format is determined automatically based on the file extension, so `.yml`
208
+ > or `.yaml` files will be handled through `YAML` and `.marshal` files through `Marshal`.
209
+
210
+ ##### Optional `rubyzip` dependency
209
211
 
210
- > When dumping into or loading from disk, the format is determined
211
- > automatically based on the file extension, so `.yml` or `.yaml` files will be
212
- > handled through `YAML` and `.marshal` files through `Marshal`.
212
+ Compressed versions of all other [supported formats](#supported-formats) are available via the [`rubyzip`][rubyzip] gem.
213
+ This is an optional dependency.
213
214
 
214
- Optionally, you can use a `.zip` version of the supported formats. In order to do so, you'll have to install
215
- the [`rubyzip`][rubyzip] gem:
215
+ > ⚠️ Rambling Trie `2.6.0` and above support `rubyzip` `3.x` only.
216
+ > For `rubyzip` `2.x` support, use Rambling Trie `2.5.1` and below.
216
217
 
217
218
  ```shell
218
219
  gem install rubyzip
220
+ # or gem install rubyzip --version '<3` for 2.x
219
221
  ```
220
222
 
221
223
  Or, include it in your `Gemfile` and bundle it:
222
224
 
223
225
  ```ruby
224
226
  gem 'rubyzip'
227
+ # or gem 'rubyzip', '<3' for 2.x
225
228
  ```
226
229
 
227
230
  Then, you can load contents form a `.zip` file like this:
@@ -274,12 +277,14 @@ want edge documentation, you can go the [GitHub project RubyDoc.info page][rubyd
274
277
 
275
278
  The Rambling Trie has been tested with the following Ruby versions:
276
279
 
280
+ * 4.0.x
281
+ * 3.4.x
277
282
  * 3.3.x
278
283
  * 3.2.x
279
- * 3.1.x
280
284
 
281
285
  **No longer supported**:
282
286
 
287
+ * 3.1.x (EOL'ed)
283
288
  * 3.0.x (EOL'ed)
284
289
  * 2.7.x (EOL'ed)
285
290
  * 2.6.x (EOL'ed)
@@ -292,9 +297,14 @@ The Rambling Trie has been tested with the following Ruby versions:
292
297
  * 1.9.x (EOL'ed)
293
298
  * 1.8.x (EOL'ed)
294
299
 
300
+ ## Compatible RBS and Steep versions
301
+
302
+ Type signatures for `Rambling::Trie` are included in the [`sig` directory](./sig)! The current version (`2.7.0`) was
303
+ checked with RBS `3.10.4` and Steep `1.10.0`.
304
+
295
305
  ## Contributing to Rambling Trie
296
306
 
297
- Take a look at the [contributing guide][rambling_trie_contributing_guide] to get started, or fire a question
307
+ Take a look at the [contributing guide](./CONTRIBUTING.md) to get started, or fire a question
298
308
  to [@gonzedge][github_user_gonzedge].
299
309
 
300
310
  ## License and copyright
@@ -316,7 +326,8 @@ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEM
316
326
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
317
327
  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
318
328
 
319
- [badge_fury_badge]: https://badge.fury.io/rb/rambling-trie.svg?version=2.5.1
329
+ [asdf]: https://asdf-vm.com/
330
+ [badge_fury_badge]: https://badge.fury.io/rb/rambling-trie.svg?version=2.7.0
320
331
  [badge_fury_link]: https://badge.fury.io/rb/rambling-trie
321
332
  [chruby]: https://github.com/postmodern/chruby
322
333
  [code_climate_grade_badge]: https://codeclimate.com/github/gonzedge/rambling-trie/badges/gpa.svg
@@ -337,9 +348,6 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
337
348
  [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
338
349
  [license_link]: https://opensource.org/licenses/mit-license.php
339
350
  [marshal]: https://ruby-doc.org/3.3.0/Marshal.html
340
- [rambling_trie_configuration]: https://github.com/gonzedge/rambling-trie#configuration
341
- [rambling_trie_contributing_guide]: https://github.com/gonzedge/rambling-trie/blob/main/CONTRIBUTING.md
342
- [rambling_trie_plain_text_reader]: https://github.com/gonzedge/rambling-trie/blob/main/lib/rambling/trie/readers/plain_text.rb
343
351
  [rbenv]: https://github.com/sstephenson/rbenv
344
352
  [rubydoc]: http://rubydoc.info/gems/rambling-trie
345
353
  [rubydoc_github]: http://rubydoc.info/github/gonzedge/rambling-trie
data/Steepfile CHANGED
@@ -13,9 +13,13 @@ target :lib do
13
13
  # check 'Rakefile'
14
14
  # check 'Steepfile'
15
15
 
16
- # library 'rubyzip'
16
+ collection_config 'rbs_collection.yaml'
17
+
18
+ library 'rubyzip'
19
+ library 'rake'
17
20
  library 'yaml'
18
21
  library 'securerandom'
22
+ library 'tmpdir'
19
23
 
20
24
  # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
21
25
  # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
@@ -6,11 +6,13 @@ module Rambling
6
6
  module Comparable
7
7
  # Compares two nodes.
8
8
  # @param [Nodes::Node] other the node to compare against.
9
- # @return [Boolean] `true` if the nodes' {Nodes::Node#letter #letter} and
10
- # {Nodes::Node#children_tree #children_tree} are equal, `false` otherwise.
9
+ # @return [Boolean] `true` if the nodes' {Nodes::Node#letter #letter},
10
+ # {Nodes::Node#terminal? #terminal?}, {Nodes::Node#value #value}, and
11
+ # {Nodes::Node#children_tree #children_tree} are all equal, `false` otherwise.
11
12
  def == other
12
13
  letter == other.letter &&
13
14
  terminal? == other.terminal? &&
15
+ value == other.value &&
14
16
  children_tree == other.children_tree
15
17
  end
16
18
  end
@@ -7,7 +7,7 @@ module Rambling
7
7
  # Indicates if the current {Rambling::Trie::Nodes::Node Node} can be compressed or not.
8
8
  # @return [Boolean] `true` for non-{Nodes::Node#terminal? terminal} nodes with one child, `false` otherwise.
9
9
  def compressible?
10
- !(root? || terminal?) && 1 == children_tree.size
10
+ !root? && !terminal? && children_tree.one?
11
11
  end
12
12
  end
13
13
  end
@@ -26,7 +26,7 @@ module Rambling
26
26
  # @param [Nodes::Node] node the node to compress.
27
27
  # @return [Nodes::Compressed] node the compressed version of the node.
28
28
  def compress_only_child_and_merge node
29
- compressed_child = compress(node.first_child) # : Nodes::Compressed
29
+ compressed_child = compress(node.first_child) || raise(InvalidOperation, 'got nil while compressing only child')
30
30
  merge node, compressed_child
31
31
  end
32
32
 
@@ -34,21 +34,33 @@ module Rambling
34
34
  letter = node.letter.to_s << other.letter.to_s
35
35
 
36
36
  compressed = Rambling::Trie::Nodes::Compressed.new letter.to_sym, node.parent, other.children_tree
37
- compressed.terminal! if other.terminal?
37
+ if other.terminal?
38
+ compressed.terminal!
39
+ value = other.value
40
+ compressed.value = value unless value.nil?
41
+ end
38
42
  compressed
39
43
  end
40
44
 
41
45
  def compress_children_and_copy node
42
46
  children_tree = compress_children(node.children_tree)
43
47
  compressed = Rambling::Trie::Nodes::Compressed.new node.letter, node.parent, children_tree
44
- compressed.terminal! if node.terminal?
48
+ if node.terminal?
49
+ compressed.terminal!
50
+ value = node.value
51
+ compressed.value = value unless value.nil?
52
+ end
45
53
  compressed
46
54
  end
47
55
 
48
56
  def compress_children tree
57
+ # @type var new_tree: Hash[Symbol, Nodes::Node]
49
58
  new_tree = {}
50
59
 
51
- tree.each { |letter, child| new_tree[letter] = compress child }
60
+ tree.each do |letter, child|
61
+ compressed_child = compress(child) || raise(InvalidOperation, "got nil while compressing #{letter}")
62
+ new_tree[letter] = compressed_child
63
+ end
52
64
 
53
65
  new_tree
54
66
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tmpdir'
4
+
3
5
  module Rambling
4
6
  module Trie
5
7
  module Configuration
@@ -39,7 +41,7 @@ module Rambling
39
41
 
40
42
  @compressor = Rambling::Trie::Compressor.new
41
43
  @root_builder = -> { Rambling::Trie::Nodes::Raw.new }
42
- @tmp_path = '/tmp'
44
+ @tmp_path = Dir.tmpdir
43
45
  end
44
46
 
45
47
  private
@@ -17,7 +17,6 @@ module Rambling
17
17
  # providers.
18
18
  # @param [TProvider] provider the provider to use as default.
19
19
  # @raise [ArgumentError] when the given provider is not in the provider collection.
20
- # @note If no providers have been configured, `nil` will be assigned.
21
20
  # @return [TProvider, nil] the default provider to use when a provider cannot be resolved in
22
21
  # {ProviderCollection#resolve #resolve}.
23
22
  attr_reader :default
@@ -25,7 +24,8 @@ module Rambling
25
24
  # Creates a new provider collection.
26
25
  # @param [Symbol] name the name for this provider collection.
27
26
  # @param [Hash<Symbol, TProvider>] providers the configured providers.
28
- # @param [TProvider, nil] default the configured default provider.
27
+ # @param [TProvider, nil] default the configured default provider. When +nil+ (or no providers
28
+ # are given), falls back to the first configured provider, or +nil+ if none exist.
29
29
  def initialize name, providers = {}, default = nil
30
30
  @name = name
31
31
  @configured_providers = providers
@@ -74,9 +74,9 @@ module Rambling
74
74
  self.default = configured_default
75
75
  end
76
76
 
77
- # Get provider corresponding to a given format.
78
- # @return [Array<Symbol>] the provider corresponding to that format.
79
- # @see https://ruby-doc.org/3.3.0/Hash.html#method-i-5B-5D
77
+ # List the formats of configured providers.
78
+ # @return [Array<Symbol>] the formats of all configured providers.
79
+ # @see https://ruby-doc.org/3.3.0/Hash.html#method-i-keys
80
80
  # Hash#keys
81
81
  def formats
82
82
  providers.keys
@@ -84,7 +84,7 @@ module Rambling
84
84
 
85
85
  # Get provider corresponding to a given format.
86
86
  # @param [Symbol] format the format to search for in the collection.
87
- # @return [TProvider] the provider corresponding to that format.
87
+ # @return [TProvider, nil] the provider corresponding to that format, or +nil+ if not found.
88
88
  # @see https://ruby-doc.org/3.3.0/Hash.html#method-i-5B-5D Hash#[]
89
89
  def [] format
90
90
  providers[format]
@@ -109,7 +109,7 @@ module Rambling
109
109
  def contains? provider
110
110
  return true if provider.nil?
111
111
 
112
- providers.any? && provider_instances.include?(provider || raise)
112
+ provider_instances.include?(provider || raise)
113
113
  end
114
114
 
115
115
  alias_method :provider_instances, :values
@@ -22,23 +22,37 @@ module Rambling
22
22
  end
23
23
 
24
24
  # Adds a word to the trie.
25
- # @param [String] word the word to add the branch from.
25
+ # @param [String] word the word to add to the trie.
26
+ # @param [Object, nil] value the value to associate with the word.
26
27
  # @return [Nodes::Node] the just added branch's root node.
27
28
  # @raise [InvalidOperation] if the trie is already compressed.
28
29
  # @see Nodes::Raw#add
29
30
  # @see Nodes::Compressed#add
30
- def add word
31
- root.add reversed_char_symbols word
31
+ def add word, value = nil
32
+ root.add reversed_char_symbols(word), value
32
33
  end
33
34
 
34
35
  # Adds all provided words to the trie.
35
- # @param [Array<String>] words the words to add the branch from.
36
+ # @param [Array<String>] words the words to add to the trie.
37
+ # @param [Array<Object>, nil] values the values to associate with each word, in the same order.
36
38
  # @return [Array<Nodes::Node>] the collection of nodes added.
37
39
  # @raise [InvalidOperation] if the trie is already compressed.
40
+ # @raise [ArgumentError] if words and values are given but differ in size.
38
41
  # @see Nodes::Raw#add
39
42
  # @see Nodes::Compressed#add
40
- def concat words
41
- words.map { |word| add word }
43
+ def concat words, values = nil
44
+ if values
45
+ words_size = words.size
46
+ values_size = values.size
47
+ unless words_size == values_size
48
+ raise ArgumentError,
49
+ "words and values must have the same size (words: #{words_size}, values: #{values_size})"
50
+ end
51
+
52
+ words.each_with_index.map { |word, index| add(word, values[index]) }
53
+ else
54
+ words.map { |word| add word }
55
+ end
42
56
  end
43
57
 
44
58
  # Compresses the existing trie using redundant node elimination.
@@ -52,10 +66,18 @@ module Rambling
52
66
  end
53
67
 
54
68
  # Compresses the existing trie using redundant node elimination. Returns a new trie with the compressed root.
55
- # @return [Container] A new {Container} with the {Nodes::Compressed Compressed} root node
56
- # or self if the trie has already been compressed.
69
+ # @return [Container] A new {Container} with the {Nodes::Compressed Compressed} root node.
70
+ # @deprecated Calling {#compress} on an already-compressed trie is deprecated and will raise
71
+ # {InvalidOperation} in the next major version. Use {#compressed?} to guard if needed.
57
72
  def compress
58
- return self if root.compressed?
73
+ if root.compressed?
74
+ warn <<~WARN.chomp.tr("\n", ' ')
75
+ [DEPRECATED] Calling `compress` on an already-compressed trie is deprecated
76
+ and will raise `InvalidOperation` in the next major version.
77
+ Called from #{caller_locations(1, 1)&.first}
78
+ WARN
79
+ return Rambling::Trie::Container.new root, compressor
80
+ end
59
81
 
60
82
  Rambling::Trie::Container.new compress_root, compressor
61
83
  end
@@ -69,7 +91,7 @@ module Rambling
69
91
  end
70
92
 
71
93
  # Adds all provided words to the trie.
72
- # @param [Array<String>] words the words to add the branch from.
94
+ # @param [String] words the words to add to the trie.
73
95
  # @return [Array<Nodes::Node>] the collection of nodes added.
74
96
  # @raise [InvalidOperation] if the trie is already compressed.
75
97
  # @see #concat
@@ -99,7 +121,6 @@ module Rambling
99
121
  # Returns all words within a string that match a word contained in the trie.
100
122
  # @param [String] phrase the string to look for matching words in.
101
123
  # @return [Array<String>] all the words in the given string that match a word in the trie.
102
- # @yield [String] each word found in phrase.
103
124
  def words_within phrase
104
125
  words_within_root(phrase).to_a
105
126
  end
@@ -126,6 +147,8 @@ module Rambling
126
147
  return enum_for :each unless block_given?
127
148
 
128
149
  root.each { |word| yield word }
150
+
151
+ self
129
152
  end
130
153
 
131
154
  # @return [String] a string representation of the container.
@@ -177,8 +200,8 @@ module Rambling
177
200
  root.key? letter
178
201
  end
179
202
 
180
- # Size of the Root {Nodes::Node Node}'s children tree.
181
- # @return [Integer] the number of letters in the root node.
203
+ # Number of words contained in the trie.
204
+ # @return [Integer] the number of words stored in the trie.
182
205
  def size
183
206
  root.size
184
207
  end
@@ -199,21 +222,23 @@ module Rambling
199
222
  return enum_for :words_within_root, phrase unless block_given?
200
223
 
201
224
  chars = phrase.chars
202
- size = chars.length
203
- # rubocop:disable Style/CommentedKeyword
204
- 0.upto(size - 1).each do |starting_index|
205
- new_phrase = chars.slice starting_index, size # : Array[String]
225
+ chars.each_index do |starting_index|
226
+ new_phrase = chars[starting_index..] || raise
206
227
  root.match_prefix(new_phrase) { |word| yield word }
207
- end # : Enumerator[String, void]
208
- # rubocop:enable Style/CommentedKeyword
228
+ end
229
+
230
+ self
209
231
  end
210
232
 
211
233
  def compress_root
212
- compressor.compress root # : Nodes::Compressed
234
+ compressor.compress(root) || raise
213
235
  end
214
236
 
215
237
  def reversed_char_symbols word
216
- word.reverse.chars.map(&:to_sym).to_a
238
+ # @type var chars: Array[String]
239
+ chars = word.chars
240
+ chars.reverse!
241
+ chars.map(&:to_sym)
217
242
  end
218
243
  end
219
244
  end
@@ -6,9 +6,6 @@ module Rambling
6
6
  module Enumerable
7
7
  include ::Enumerable
8
8
 
9
- # Empty enumerator constant for early each exits.
10
- EMPTY_ENUMERATOR = [].to_enum :each
11
-
12
9
  # Returns number of words contained in the trie
13
10
  # @see https://ruby-doc.org/3.3.0/Enumerable.html#method-i-count Enumerable#count
14
11
  alias_method :size, :count
@@ -25,6 +22,14 @@ module Rambling
25
22
 
26
23
  self
27
24
  end
25
+
26
+ # Returns a new empty enumerator for early-exit returns.
27
+ # A method rather than a constant to prevent shared mutable state.
28
+ def empty_enum
29
+ # @type var empty_array: Array[String]
30
+ empty_array = []
31
+ empty_array.each
32
+ end
28
33
  end
29
34
  end
30
35
  end
@@ -18,15 +18,20 @@ module Rambling
18
18
  def attributes
19
19
  [
20
20
  letter_inspect,
21
+ value_inspect,
21
22
  terminal_inspect,
22
23
  children_inspect,
23
- ].join ', '
24
+ ].compact.join ', '
24
25
  end
25
26
 
26
27
  def letter_inspect
27
28
  "letter: #{letter.inspect}"
28
29
  end
29
30
 
31
+ def value_inspect
32
+ value && "value: #{value.inspect}"
33
+ end
34
+
30
35
  def terminal_inspect
31
36
  "terminal: #{terminal.inspect}"
32
37
  end
@@ -3,7 +3,7 @@
3
3
  module Rambling
4
4
  module Trie
5
5
  module Nodes
6
- # A representation of a node in an compressed trie data structure.
6
+ # A representation of a node in a compressed trie data structure.
7
7
  # :reek:RepeatedConditional { max_ifs: 4 }
8
8
  class Compressed < Rambling::Trie::Nodes::Node
9
9
  # Creates a new compressed node.
@@ -19,10 +19,10 @@ module Rambling
19
19
 
20
20
  # Always raises {Rambling::Trie::InvalidOperation InvalidOperation} when
21
21
  # trying to add a word to the current compressed trie node
22
- # @param [String] _ the word to add to the trie.
23
- # @raise [InvalidOperation] if the trie is already compressed.
24
- # @return [void]
25
- def add _
22
+ # @param [Array<Symbol>] _word the word chars to add to the trie.
23
+ # @param [Object, nil] _value the value to associate with the word.
24
+ # @raise [InvalidOperation] always.
25
+ def add _word, _value = nil
26
26
  raise Rambling::Trie::InvalidOperation, 'Cannot add word to compressed trie'
27
27
  end
28
28
 
@@ -42,12 +42,12 @@ module Rambling
42
42
 
43
43
  if chars.size >= child_letter.size
44
44
  letter = (chars.shift(child_letter.size) || raise).join
45
- return child.partial_word? chars if child_letter == letter
46
- end
45
+ return false unless child_letter == letter
47
46
 
48
- letter = chars.join
49
- child_letter = child_letter.slice 0, letter.size
50
- child_letter == letter
47
+ child.partial_word? chars
48
+ else
49
+ child_letter.start_with? chars.join
50
+ end
51
51
  end
52
52
 
53
53
  def word_chars? chars
@@ -75,29 +75,37 @@ module Rambling
75
75
 
76
76
  child_letter = child.letter.to_s
77
77
 
78
- if chars.size >= child_letter.size
79
- letter = (chars.shift(child_letter.size) || raise).join
80
- return child.scan chars if child_letter == letter
78
+ if chars.size < child_letter.size
79
+ return child_letter.start_with?(chars.join) ? child : missing
81
80
  end
82
81
 
83
- letter = chars.join
84
- child_letter = child_letter.slice 0, letter.size
82
+ letter = (chars.shift(child_letter.size) || raise).join
83
+ return missing unless child_letter == letter
85
84
 
86
- child_letter == letter ? child : missing
85
+ child.scan chars
87
86
  end
88
87
 
89
88
  def children_match_prefix chars
90
89
  return enum_for :children_match_prefix, chars unless block_given?
91
90
 
92
- return EMPTY_ENUMERATOR if chars.empty?
91
+ return empty_enum if chars.empty?
93
92
 
94
93
  child = children_tree[(chars.first || raise).to_sym]
95
- return EMPTY_ENUMERATOR unless child
94
+ return empty_enum unless child
96
95
 
96
+ match_child_prefix(child, chars) { |word| yield word }
97
+ end
98
+
99
+ def match_child_prefix child, chars
97
100
  child_letter = child.letter.to_s
101
+
102
+ # stop early if we already know that the remaining characters in the
103
+ # given phrase do not even cover the current node's compressed key
104
+ return empty_enum if chars.size < child_letter.size
105
+
98
106
  letter = (chars.shift(child_letter.size) || raise).join
99
107
 
100
- return EMPTY_ENUMERATOR unless child_letter == letter
108
+ return empty_enum unless child_letter == letter
101
109
 
102
110
  child.match_prefix(chars) { |word| yield word }
103
111
  end
@@ -5,6 +5,23 @@ module Rambling
5
5
  module Nodes
6
6
  # A representation of a missing node in the trie data structure. Returned when a node is not found.
7
7
  class Missing < Rambling::Trie::Nodes::Node
8
+ def partial_word? _
9
+ false
10
+ end
11
+
12
+ private
13
+
14
+ def word_chars? _
15
+ false
16
+ end
17
+
18
+ def closest_node _
19
+ self
20
+ end
21
+
22
+ def children_match_prefix chars
23
+ enum_for :children_match_prefix, chars unless block_given?
24
+ end
8
25
  end
9
26
  end
10
27
  end