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.
- checksums.yaml +4 -4
- data/Dockerfile +2 -1
- data/Gemfile +2 -0
- data/README.md +28 -20
- data/Steepfile +5 -1
- data/lib/rambling/trie/comparable.rb +4 -2
- data/lib/rambling/trie/compressible.rb +1 -1
- data/lib/rambling/trie/compressor.rb +16 -4
- data/lib/rambling/trie/configuration/properties.rb +3 -1
- data/lib/rambling/trie/configuration/provider_collection.rb +7 -7
- data/lib/rambling/trie/container.rb +46 -21
- data/lib/rambling/trie/enumerable.rb +8 -3
- data/lib/rambling/trie/inspectable.rb +6 -1
- data/lib/rambling/trie/nodes/compressed.rb +27 -19
- data/lib/rambling/trie/nodes/missing.rb +17 -0
- data/lib/rambling/trie/nodes/node.rb +28 -21
- data/lib/rambling/trie/nodes/raw.rb +11 -8
- data/lib/rambling/trie/not_implemented.rb +23 -0
- data/lib/rambling/trie/readers/plain_text.rb +4 -1
- data/lib/rambling/trie/readers/reader.rb +5 -2
- data/lib/rambling/trie/serializers/marshal.rb +5 -0
- data/lib/rambling/trie/serializers/serializer.rb +8 -4
- data/lib/rambling/trie/serializers/yaml.rb +5 -0
- data/lib/rambling/trie/serializers/zip.rb +31 -21
- data/lib/rambling/trie/stringifyable.rb +2 -5
- data/lib/rambling/trie/version.rb +1 -1
- data/lib/rambling/trie.rb +14 -10
- data/rambling-trie.gemspec +1 -1
- data/sig/lib/nilable.rbs +3 -0
- data/sig/lib/rambling/trie/comparable.rbs +6 -4
- data/sig/lib/rambling/trie/compressible.rbs +2 -2
- data/sig/lib/rambling/trie/compressor.rbs +6 -6
- data/sig/lib/rambling/trie/configuration/properties.rbs +5 -5
- data/sig/lib/rambling/trie/configuration/provider_collection.rbs +0 -4
- data/sig/lib/rambling/trie/container.rbs +24 -23
- data/sig/lib/rambling/trie/enumerable.rbs +6 -5
- data/sig/lib/rambling/trie/inspectable.rbs +8 -3
- data/sig/lib/rambling/trie/nodes/compressed.rbs +6 -4
- data/sig/lib/rambling/trie/nodes/missing.rbs +10 -2
- data/sig/lib/rambling/trie/nodes/node.rbs +21 -19
- data/sig/lib/rambling/trie/nodes/raw.rbs +5 -5
- data/sig/lib/rambling/trie/not_implemented.rbs +10 -0
- data/sig/lib/rambling/trie/readers/plain_text.rbs +2 -2
- data/sig/lib/rambling/trie/readers/reader.rbs +2 -0
- data/sig/lib/rambling/trie/serializers/marshal.rbs +1 -1
- data/sig/lib/rambling/trie/serializers/serializer.rbs +2 -0
- data/sig/lib/rambling/trie/serializers/yaml.rbs +1 -1
- data/sig/lib/rambling/trie/serializers/zip.rbs +8 -6
- data/sig/lib/rambling/trie/stringifyable.rbs +4 -4
- data/sig/lib/rambling/trie.rbs +10 -10
- metadata +8 -10
- data/sig/lib/zip/entry.rbs +0 -11
- data/sig/lib/zip/file.rbs +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a9d76d13a38a54c80ca90236346dcfe280fea62d46d8e9318efc8f2653b6f40
|
|
4
|
+
data.tar.gz: 0ab262b5f456d0e299fc16614bb0c7103406899b0a88dce3ef69d0d1f403fd3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13ccb7f63311ae91544442fe75e1620ecb91ef87501150fa46f454f80b7b4d51cda69ebeb8c82e474173db815e5f536b9cf03cccb3fa8e6233facfbd7f265949
|
|
7
|
+
data.tar.gz: dfab4eaea2613f6ad5957579089056441d3f01533112fff081504da2be2e18f77267350f6cedd4c61d65d88075cf0e83d22e5124370fac45fa2dadc36b22ddcb
|
data/Dockerfile
CHANGED
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.
|
|
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]
|
|
81
|
-
an example, and at the [Configuration section]
|
|
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
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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]
|
|
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
|
-
[
|
|
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
|
-
|
|
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}
|
|
10
|
-
# {Nodes::Node#
|
|
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
|
-
!
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
#
|
|
78
|
-
# @return [Array<Symbol>] the
|
|
79
|
-
# @see https://ruby-doc.org/3.3.0/Hash.html#method-i-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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 [
|
|
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
|
-
#
|
|
181
|
-
# @return [Integer] the number of
|
|
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
|
-
|
|
203
|
-
|
|
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
|
|
208
|
-
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
self
|
|
209
231
|
end
|
|
210
232
|
|
|
211
233
|
def compress_root
|
|
212
|
-
compressor.compress
|
|
234
|
+
compressor.compress(root) || raise
|
|
213
235
|
end
|
|
214
236
|
|
|
215
237
|
def reversed_char_symbols word
|
|
216
|
-
|
|
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
|
|
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 [
|
|
23
|
-
# @
|
|
24
|
-
# @
|
|
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
|
|
46
|
-
end
|
|
45
|
+
return false unless child_letter == letter
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
79
|
-
|
|
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
|
-
|
|
82
|
+
letter = (chars.shift(child_letter.size) || raise).join
|
|
83
|
+
return missing unless child_letter == letter
|
|
85
84
|
|
|
86
|
-
|
|
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
|
|
91
|
+
return empty_enum if chars.empty?
|
|
93
92
|
|
|
94
93
|
child = children_tree[(chars.first || raise).to_sym]
|
|
95
|
-
return
|
|
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
|
|
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
|