rbtree-ruby 0.3.4 → 0.3.6
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/CHANGELOG.md +35 -0
- data/README.ja.md +51 -4
- data/README.md +51 -4
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +442 -288
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18d0cdfd5f115b61096c266d9de0a482e263574aad1dfce425efaabd3dad67b7
|
|
4
|
+
data.tar.gz: 639a4f4f85c14cab4acf93f5b9b5280fb6225c96c575cac1a47b2b4f934dca8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e40c1a174c7f2dd1c6df7f94e316070178457e0baa41322baa0f622157eb548245bfd334435488d3a49cfe8396e8d587ae80f886a17dadbf93cd9f03fcd701d7
|
|
7
|
+
data.tar.gz: 20f49f91135a98e4b46ccb4b0981dafab6bb35a9375bfb06024b7b42f5c71ad04b23613ad5a1f372ac6456d9edf03ef9698b68ca2228e0cf5cb85d6c2ae2b5f4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.6] - 2026-01-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
> **Note:** All methods added in this release are **convenience methods** composed from existing public API primitives (`each`, `insert`, `delete`, `dup`, `merge!`, `safe: true`). They provide no speed advantage over manual composition — their value is purely in readability and API completeness.
|
|
13
|
+
|
|
14
|
+
- **`dup` (Deep Copy)**: `RBTree` and `MultiRBTree` now support `dup` and `clone` via `initialize_copy`. Creates an independent deep copy of the tree — modifications to the copy do not affect the original and vice versa.
|
|
15
|
+
- **`select`**: Returns a new tree containing only key-value pairs for which the block returns true. Returns an `Enumerator` if no block is given.
|
|
16
|
+
- **`reject`**: Returns a new tree excluding key-value pairs for which the block returns true. Returns an `Enumerator` if no block is given.
|
|
17
|
+
- **`delete_if`**: Deletes key-value pairs in place for which the block returns true. Returns self. In `MultiRBTree`, operates at individual value granularity (can remove specific values without deleting the entire key).
|
|
18
|
+
- **`reject!`**: Same as `delete_if`, but returns `nil` if no changes were made.
|
|
19
|
+
- **`keep_if`**: Keeps only key-value pairs for which the block returns true, deleting the rest in place. Returns self. In `MultiRBTree`, operates at individual value granularity.
|
|
20
|
+
- **`invert`**: Returns a new tree with keys and values swapped. For `RBTree`, duplicate values result in the last key winning (same as `Hash#invert`). For `MultiRBTree`, all pairs are preserved.
|
|
21
|
+
- **`merge`** (non-destructive): Returns a new tree with merged contents. Supports block for duplicate key resolution: `tree.merge(other) { |key, v1, v2| v1 }`.
|
|
22
|
+
- **`merge!` block support**: `merge!` now accepts a block for duplicate key resolution, matching `Hash#merge!` behavior.
|
|
23
|
+
|
|
24
|
+
### Optimized
|
|
25
|
+
- **`MultiRBTree#delete_if` / `#keep_if`**: Replaced clear-and-rebuild approach with in-place value array filtering via internal `filter_values!`. Avoids O(n log n) tree reconstruction; now runs in O(n) by directly mutating each node's value array and removing only emptied nodes.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **`MultiRBTree#clear`**: Fixed `@value_count` not being reset when `clear` was called, which caused `size` to return incorrect values after clearing and re-inserting.
|
|
29
|
+
|
|
30
|
+
## [0.3.5] - 2026-01-26
|
|
31
|
+
|
|
32
|
+
### Optimized
|
|
33
|
+
- **O(1) Max Access**: Added cached `@max_node` to ensure `max` and `pop` operations run in constant time.
|
|
34
|
+
- **Traversal Engine Rewrite**: Optimized all range queries (`lt`, `gt`, `between`, `each`) with a unified traversal engine featuring $O(1)$ range rejection and bound optimization.
|
|
35
|
+
- **Fast-path Startup**: Range queries now leverage the hybrid Hash index to jump directly to target nodes (even for exclusive bounds), eliminating $O(\log N)$ tree descents when keys exist.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- **Defensive Assertion**: Added an explicit check to `find_nearest_node` to ensure only `Numeric` keys are accepted, formalizing a pre-existing design constraint.
|
|
39
|
+
- **Internal Refactoring**: Unified the insertion logic between `RBTree` and `MultiRBTree` into a single internal method to improve maintainability and ensure consistent optimization.
|
|
40
|
+
|
|
8
41
|
## [0.3.4] - 2026-01-25
|
|
9
42
|
|
|
10
43
|
### Added
|
|
@@ -219,6 +252,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
219
252
|
- ASCII diagrams for tree rotation operations
|
|
220
253
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
221
254
|
|
|
255
|
+
[0.4.0]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.4.0
|
|
256
|
+
[0.3.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.5
|
|
222
257
|
[0.3.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.4
|
|
223
258
|
[0.3.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.3
|
|
224
259
|
[0.3.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.2
|
data/README.ja.md
CHANGED
|
@@ -197,10 +197,58 @@ tree.to_a # => [[1, "one"], [2, "two"]]
|
|
|
197
197
|
# ハッシュへの変換
|
|
198
198
|
tree.to_h # => {1 => "one", 2 => "two"}
|
|
199
199
|
|
|
200
|
-
#
|
|
200
|
+
# 結合(破壊的)
|
|
201
201
|
other = {3 => 'three'}
|
|
202
202
|
tree.merge!(other)
|
|
203
203
|
tree.size # => 3
|
|
204
|
+
|
|
205
|
+
# 結合(非破壊) — 新しいツリーを返す
|
|
206
|
+
merged = tree.merge({4 => 'four'})
|
|
207
|
+
|
|
208
|
+
# ブロックで重複キーの解決
|
|
209
|
+
merged = tree.merge({1 => 'ONE'}) { |key, old_val, new_val| old_val }
|
|
210
|
+
|
|
211
|
+
# キーと値を入れ替え
|
|
212
|
+
tree = RBTree.new({1 => 'a', 2 => 'b', 3 => 'c'})
|
|
213
|
+
tree.invert.to_a # => [["a", 1], ["b", 2], ["c", 3]]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### フィルタリングとコピー
|
|
217
|
+
|
|
218
|
+
> **注記:** `dup`、`select`、`reject`、`delete_if`、`reject!`、`keep_if`、`invert`、`merge` は既存のプリミティブを組み合わせた利便性メソッドです。手動で同等の処理を書いた場合と比べて速度上の利点はありません。可読性とAPI互換性のために提供されています。
|
|
219
|
+
|
|
220
|
+
Rubyの慣習的なメソッドでツリーのコピーやフィルタリングが可能です:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
224
|
+
|
|
225
|
+
# ディープコピー — 元のツリーと独立
|
|
226
|
+
copy = tree.dup
|
|
227
|
+
copy.delete(1)
|
|
228
|
+
tree.size # => 4 (変更なし)
|
|
229
|
+
|
|
230
|
+
# select / reject — 新しいツリーを返す
|
|
231
|
+
evens = tree.select { |k, _| k.even? } # => {2=>"two", 4=>"four"}
|
|
232
|
+
odds = tree.reject { |k, _| k.even? } # => {1=>"one", 3=>"three"}
|
|
233
|
+
|
|
234
|
+
# delete_if / keep_if — 破壊的に変更
|
|
235
|
+
tree.delete_if { |k, _| k > 2 }
|
|
236
|
+
tree.to_a # => [[1, "one"], [2, "two"]]
|
|
237
|
+
|
|
238
|
+
# reject! — delete_ifと同様だが、変更がなければnilを返す
|
|
239
|
+
tree.reject! { |_, _| false } # => nil
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`MultiRBTree`では、`delete_if`と`keep_if`は個々の値単位で操作します:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
tree = MultiRBTree.new
|
|
246
|
+
tree.insert(1, 'a')
|
|
247
|
+
tree.insert(1, 'b')
|
|
248
|
+
tree.insert(2, 'c')
|
|
249
|
+
|
|
250
|
+
tree.delete_if { |k, v| k == 1 && v == 'a' }
|
|
251
|
+
tree.to_a # => [[1, "b"], [2, "c"]] — 'a'のみ削除された
|
|
204
252
|
```
|
|
205
253
|
|
|
206
254
|
### MultiRBTree 値配列アクセス
|
|
@@ -239,10 +287,9 @@ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
|
|
|
239
287
|
- `delete(key)` - O(log n)
|
|
240
288
|
- `value(key)` / `[]` - **O(1)** (内部ハッシュインデックスによる超高速アクセス)
|
|
241
289
|
- `has_key?` - **O(1)** (内部ハッシュインデックスによる超高速チェック)
|
|
242
|
-
- `min` - **O(1)**
|
|
243
|
-
- `max` - O(log n)
|
|
290
|
+
- `min` / `max` - **O(1)**
|
|
244
291
|
- `shift` / `pop` - O(log n)
|
|
245
|
-
- `prev` / `succ` - O(log n)、O(1)
|
|
292
|
+
- `prev` / `succ` - O(log n)、O(1)ハッシュチェックと高速な走査開始により改善
|
|
246
293
|
|
|
247
294
|
全要素のイテレーションはO(n)時間。
|
|
248
295
|
|
data/README.md
CHANGED
|
@@ -197,10 +197,58 @@ tree.to_a # => [[1, "one"], [2, "two"]]
|
|
|
197
197
|
# Convert to Hash
|
|
198
198
|
tree.to_h # => {1 => "one", 2 => "two"}
|
|
199
199
|
|
|
200
|
-
# Merge
|
|
200
|
+
# Merge (destructive)
|
|
201
201
|
other = {3 => 'three'}
|
|
202
202
|
tree.merge!(other)
|
|
203
203
|
tree.size # => 3
|
|
204
|
+
|
|
205
|
+
# Merge (non-destructive) — returns a new tree
|
|
206
|
+
merged = tree.merge({4 => 'four'})
|
|
207
|
+
|
|
208
|
+
# Merge with block for duplicate key resolution
|
|
209
|
+
merged = tree.merge({1 => 'ONE'}) { |key, old_val, new_val| old_val }
|
|
210
|
+
|
|
211
|
+
# Invert keys and values
|
|
212
|
+
tree = RBTree.new({1 => 'a', 2 => 'b', 3 => 'c'})
|
|
213
|
+
tree.invert.to_a # => [["a", 1], ["b", 2], ["c", 3]]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Filtering and Copying
|
|
217
|
+
|
|
218
|
+
> **Note:** `dup`, `select`, `reject`, `delete_if`, `reject!`, `keep_if`, `invert`, and `merge` are convenience methods composed from existing primitives. They provide no speed advantage over manual composition — their value is in readability and API completeness.
|
|
219
|
+
|
|
220
|
+
Create copies or filter trees using familiar Ruby idioms:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
224
|
+
|
|
225
|
+
# Deep copy — independent of original
|
|
226
|
+
copy = tree.dup
|
|
227
|
+
copy.delete(1)
|
|
228
|
+
tree.size # => 4 (unchanged)
|
|
229
|
+
|
|
230
|
+
# select / reject — return a new tree
|
|
231
|
+
evens = tree.select { |k, _| k.even? } # => {2=>"two", 4=>"four"}
|
|
232
|
+
odds = tree.reject { |k, _| k.even? } # => {1=>"one", 3=>"three"}
|
|
233
|
+
|
|
234
|
+
# delete_if / keep_if — modify in place
|
|
235
|
+
tree.delete_if { |k, _| k > 2 }
|
|
236
|
+
tree.to_a # => [[1, "one"], [2, "two"]]
|
|
237
|
+
|
|
238
|
+
# reject! — like delete_if, but returns nil if nothing changed
|
|
239
|
+
tree.reject! { |_, _| false } # => nil
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
For `MultiRBTree`, `delete_if` and `keep_if` operate at individual value granularity:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
tree = MultiRBTree.new
|
|
246
|
+
tree.insert(1, 'a')
|
|
247
|
+
tree.insert(1, 'b')
|
|
248
|
+
tree.insert(2, 'c')
|
|
249
|
+
|
|
250
|
+
tree.delete_if { |k, v| k == 1 && v == 'a' }
|
|
251
|
+
tree.to_a # => [[1, "b"], [2, "c"]] — only 'a' was removed
|
|
204
252
|
```
|
|
205
253
|
|
|
206
254
|
### MultiRBTree Value Array Access
|
|
@@ -239,10 +287,9 @@ All major operations run in **O(log n)** time:
|
|
|
239
287
|
- `delete(key)` - O(log n)
|
|
240
288
|
- `value(key)` / `[]` - **O(1)** (hybrid hash index)
|
|
241
289
|
- `has_key?` - **O(1)** (hybrid hash index)
|
|
242
|
-
- `min` - **O(1)**
|
|
243
|
-
- `max` - O(log n)
|
|
290
|
+
- `min` / `max` - **O(1)**
|
|
244
291
|
- `shift` / `pop` - O(log n)
|
|
245
|
-
- `prev` / `succ` - O(log n) with O(1) hash check
|
|
292
|
+
- `prev` / `succ` - O(log n) with O(1) hash check and faster startup
|
|
246
293
|
|
|
247
294
|
Iteration over all elements takes O(n) time.
|
|
248
295
|
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -99,6 +99,7 @@ class RBTree
|
|
|
99
99
|
@nil_node.right = @nil_node
|
|
100
100
|
@root = @nil_node
|
|
101
101
|
@min_node = @nil_node
|
|
102
|
+
@max_node = @nil_node
|
|
102
103
|
@hash_index = {} # Hash index for O(1) key lookup
|
|
103
104
|
@node_allocator = node_allocator
|
|
104
105
|
@key_count = 0
|
|
@@ -110,6 +111,16 @@ class RBTree
|
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
|
|
114
|
+
# Creates a deep copy of the tree.
|
|
115
|
+
# Called automatically by `dup` and `clone`.
|
|
116
|
+
#
|
|
117
|
+
# @param orig [RBTree] the original tree to copy
|
|
118
|
+
# @return [void]
|
|
119
|
+
def initialize_copy(orig)
|
|
120
|
+
initialize(overwrite: orig.instance_variable_get(:@overwrite))
|
|
121
|
+
orig.each { |k, v| insert(k, v) }
|
|
122
|
+
end
|
|
123
|
+
|
|
113
124
|
# Returns a Hash containing all key-value pairs from the tree.
|
|
114
125
|
#
|
|
115
126
|
# @return [Hash] a new Hash with the tree's contents
|
|
@@ -368,16 +379,43 @@ class RBTree
|
|
|
368
379
|
end
|
|
369
380
|
alias :[]= :insert
|
|
370
381
|
|
|
382
|
+
# Returns a new tree containing the merged contents of self and other.
|
|
383
|
+
#
|
|
384
|
+
# When a block is given, it is called with (key, old_value, new_value) for
|
|
385
|
+
# duplicate keys, and the block's return value is used.
|
|
386
|
+
#
|
|
387
|
+
# @param other [RBTree, Hash, Enumerable] the source to merge from
|
|
388
|
+
# @yield [key, old_value, new_value] called for duplicate keys when block given
|
|
389
|
+
# @return [RBTree] a new tree with merged contents
|
|
390
|
+
def merge(other, &block)
|
|
391
|
+
dup.merge!(other, &block)
|
|
392
|
+
end
|
|
393
|
+
|
|
371
394
|
# Merges the contents of another tree, hash, or enumerable into this tree.
|
|
372
395
|
#
|
|
396
|
+
# When a block is given, it is called with (key, old_value, new_value) for
|
|
397
|
+
# duplicate keys, and the block's return value is used.
|
|
398
|
+
#
|
|
373
399
|
# @param other [RBTree, Hash, Enumerable] the source to merge from
|
|
374
|
-
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
400
|
+
# @param overwrite [Boolean] whether to overwrite existing keys (default: true). Ignored if block given.
|
|
401
|
+
# @yield [key, old_value, new_value] called for duplicate keys when block given
|
|
375
402
|
# @return [RBTree] self
|
|
376
|
-
def merge!(other, overwrite: true)
|
|
403
|
+
def merge!(other, overwrite: true, &block)
|
|
377
404
|
if defined?(MultiRBTree) && other.is_a?(MultiRBTree)
|
|
378
405
|
raise ArgumentError, "Cannot merge MultiRBTree into RBTree"
|
|
379
406
|
end
|
|
380
|
-
|
|
407
|
+
if block
|
|
408
|
+
other_enum = other.is_a?(Hash) || other.is_a?(RBTree) ? other : other.each
|
|
409
|
+
other_enum.each do |k, v|
|
|
410
|
+
if has_key?(k)
|
|
411
|
+
insert_entry(k, block.call(k, value(k), v), overwrite: true)
|
|
412
|
+
else
|
|
413
|
+
insert_entry(k, v)
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
else
|
|
417
|
+
insert(other, overwrite: overwrite)
|
|
418
|
+
end
|
|
381
419
|
self
|
|
382
420
|
end
|
|
383
421
|
|
|
@@ -418,7 +456,7 @@ class RBTree
|
|
|
418
456
|
# tree.pop # => [3, "three"]
|
|
419
457
|
# tree.pop # => [2, "two"]
|
|
420
458
|
def pop
|
|
421
|
-
return nil unless (n =
|
|
459
|
+
return nil unless (n = @max_node) != @nil_node
|
|
422
460
|
pair = n.pair
|
|
423
461
|
delete(n.key)
|
|
424
462
|
pair
|
|
@@ -428,7 +466,7 @@ class RBTree
|
|
|
428
466
|
#
|
|
429
467
|
# @return [RBTree] self
|
|
430
468
|
def clear
|
|
431
|
-
@root = @min_node = @nil_node
|
|
469
|
+
@root = @min_node = @max_node = @nil_node
|
|
432
470
|
@hash_index.clear
|
|
433
471
|
@key_count = 0
|
|
434
472
|
self
|
|
@@ -484,11 +522,7 @@ class RBTree
|
|
|
484
522
|
# end
|
|
485
523
|
def each(reverse: false, safe: false, &block)
|
|
486
524
|
return enum_for(__method__, reverse: reverse, safe: safe) { size } unless block_given?
|
|
487
|
-
|
|
488
|
-
traverse_all_desc(@root, safe: safe, &block)
|
|
489
|
-
else
|
|
490
|
-
traverse_all_asc(@root, safe: safe, &block)
|
|
491
|
-
end
|
|
525
|
+
traverse_range(reverse, nil, nil, false, false, safe: safe, &block)
|
|
492
526
|
self
|
|
493
527
|
end
|
|
494
528
|
|
|
@@ -530,11 +564,7 @@ class RBTree
|
|
|
530
564
|
# tree.lt(3, safe: true) { |k, _| tree.delete(k) if k.even? } # safe to delete
|
|
531
565
|
def lt(key, reverse: false, safe: false, &block)
|
|
532
566
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
533
|
-
|
|
534
|
-
traverse_lt_desc(@root, key, safe: safe, &block)
|
|
535
|
-
else
|
|
536
|
-
traverse_lt_asc(@root, key, safe: safe, &block)
|
|
537
|
-
end
|
|
567
|
+
traverse_range(reverse, nil, key, false, false, safe: safe, &block)
|
|
538
568
|
self
|
|
539
569
|
end
|
|
540
570
|
|
|
@@ -551,11 +581,7 @@ class RBTree
|
|
|
551
581
|
# tree.lte(3, reverse: true).first # => [3, "three"]
|
|
552
582
|
def lte(key, reverse: false, safe: false, &block)
|
|
553
583
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
554
|
-
|
|
555
|
-
traverse_lte_desc(@root, key, safe: safe, &block)
|
|
556
|
-
else
|
|
557
|
-
traverse_lte_asc(@root, key, safe: safe, &block)
|
|
558
|
-
end
|
|
584
|
+
traverse_range(reverse, nil, key, false, true, safe: safe, &block)
|
|
559
585
|
self
|
|
560
586
|
end
|
|
561
587
|
|
|
@@ -572,11 +598,7 @@ class RBTree
|
|
|
572
598
|
# tree.gt(2, reverse: true).first # => [4, "four"]
|
|
573
599
|
def gt(key, reverse: false, safe: false, &block)
|
|
574
600
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
575
|
-
|
|
576
|
-
traverse_gt_desc(@root, key, safe: safe, &block)
|
|
577
|
-
else
|
|
578
|
-
traverse_gt_asc(@root, key, safe: safe, &block)
|
|
579
|
-
end
|
|
601
|
+
traverse_range(reverse, key, nil, false, false, safe: safe, &block)
|
|
580
602
|
self
|
|
581
603
|
end
|
|
582
604
|
|
|
@@ -593,11 +615,7 @@ class RBTree
|
|
|
593
615
|
# tree.gte(2, reverse: true).first # => [4, "four"]
|
|
594
616
|
def gte(key, reverse: false, safe: false, &block)
|
|
595
617
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
596
|
-
|
|
597
|
-
traverse_gte_desc(@root, key, safe: safe, &block)
|
|
598
|
-
else
|
|
599
|
-
traverse_gte_asc(@root, key, safe: safe, &block)
|
|
600
|
-
end
|
|
618
|
+
traverse_range(reverse, key, nil, true, false, safe: safe, &block)
|
|
601
619
|
self
|
|
602
620
|
end
|
|
603
621
|
|
|
@@ -617,14 +635,76 @@ class RBTree
|
|
|
617
635
|
# tree.between(2, 4, reverse: true).first # => [4, "four"]
|
|
618
636
|
def between(min, max, include_min: true, include_max: true, reverse: false, safe: false, &block)
|
|
619
637
|
return enum_for(__method__, min, max, include_min: include_min, include_max: include_max, reverse: reverse, safe: safe) unless block_given?
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
638
|
+
traverse_range(reverse, min, max, include_min, include_max, safe: safe, &block)
|
|
639
|
+
self
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Returns a new tree containing key-value pairs for which the block returns true.
|
|
643
|
+
#
|
|
644
|
+
# @yield [key, value] each key-value pair
|
|
645
|
+
# @return [RBTree, Enumerator] a new tree with selected pairs, or Enumerator if no block
|
|
646
|
+
def select(&block)
|
|
647
|
+
return enum_for(__method__) { size } unless block_given?
|
|
648
|
+
result = self.class.new
|
|
649
|
+
each { |k, v| result.insert(k, v) if block.call(k, v) }
|
|
650
|
+
result
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
# Returns a new tree containing key-value pairs for which the block returns false.
|
|
654
|
+
#
|
|
655
|
+
# @yield [key, value] each key-value pair
|
|
656
|
+
# @return [RBTree, Enumerator] a new tree with non-rejected pairs, or Enumerator if no block
|
|
657
|
+
def reject(&block)
|
|
658
|
+
return enum_for(__method__) { size } unless block_given?
|
|
659
|
+
result = self.class.new
|
|
660
|
+
each { |k, v| result.insert(k, v) unless block.call(k, v) }
|
|
661
|
+
result
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# Deletes key-value pairs for which the block returns true. Returns nil if no changes were made.
|
|
665
|
+
#
|
|
666
|
+
# @yield [key, value] each key-value pair
|
|
667
|
+
# @return [RBTree, nil, Enumerator] self if changed, nil if unchanged, or Enumerator if no block
|
|
668
|
+
def reject!(&block)
|
|
669
|
+
return enum_for(__method__) { size } unless block_given?
|
|
670
|
+
size_before = size
|
|
671
|
+
delete_if(&block)
|
|
672
|
+
size == size_before ? nil : self
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Keeps key-value pairs for which the block returns true, deleting the rest. Modifies the tree in place.
|
|
676
|
+
#
|
|
677
|
+
# @yield [key, value] each key-value pair
|
|
678
|
+
# @return [RBTree, Enumerator] self, or Enumerator if no block
|
|
679
|
+
def keep_if(&block)
|
|
680
|
+
return enum_for(__method__) { size } unless block_given?
|
|
681
|
+
each(safe: true) { |k, v| delete(k) unless block.call(k, v) }
|
|
625
682
|
self
|
|
626
683
|
end
|
|
627
684
|
|
|
685
|
+
# Deletes key-value pairs for which the block returns true. Modifies the tree in place.
|
|
686
|
+
#
|
|
687
|
+
# @yield [key, value] each key-value pair
|
|
688
|
+
# @return [RBTree, Enumerator] self, or Enumerator if no block
|
|
689
|
+
def delete_if(&block)
|
|
690
|
+
return enum_for(__method__) { size } unless block_given?
|
|
691
|
+
each(safe: true) { |k, v| delete(k) if block.call(k, v) }
|
|
692
|
+
self
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# Returns a new tree with keys and values swapped.
|
|
696
|
+
#
|
|
697
|
+
# For RBTree, duplicate values result in later keys overwriting earlier ones.
|
|
698
|
+
# For MultiRBTree, all key-value pairs are preserved.
|
|
699
|
+
# Values must implement <=> to serve as keys in the new tree.
|
|
700
|
+
#
|
|
701
|
+
# @return [RBTree, MultiRBTree] a new tree with keys and values inverted
|
|
702
|
+
def invert
|
|
703
|
+
result = self.class.new
|
|
704
|
+
each { |k, v| result.insert(v, k) }
|
|
705
|
+
result
|
|
706
|
+
end
|
|
707
|
+
|
|
628
708
|
# Returns a string representation of the tree.
|
|
629
709
|
#
|
|
630
710
|
# Shows the first 5 entries and total size. Useful for debugging.
|
|
@@ -655,9 +735,8 @@ class RBTree
|
|
|
655
735
|
# @!visibility private
|
|
656
736
|
private
|
|
657
737
|
|
|
658
|
-
def min_node = (
|
|
659
|
-
|
|
660
|
-
def max_node = ((n = rightmost(@root)) == @nil_node) ? nil : n
|
|
738
|
+
def min_node = (@min_node == @nil_node) ? nil : @min_node
|
|
739
|
+
def max_node = (@max_node == @nil_node) ? nil : @max_node
|
|
661
740
|
|
|
662
741
|
# Inserts a single key-value pair.
|
|
663
742
|
#
|
|
@@ -666,27 +745,51 @@ class RBTree
|
|
|
666
745
|
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
667
746
|
# @return [Boolean, nil] true if inserted/updated, nil if key exists and overwrite is false
|
|
668
747
|
def insert_entry(key, value, overwrite: true)
|
|
748
|
+
insert_entry_generic(key) do |node, is_new|
|
|
749
|
+
if is_new
|
|
750
|
+
value
|
|
751
|
+
else
|
|
752
|
+
if overwrite
|
|
753
|
+
node.value = value
|
|
754
|
+
true
|
|
755
|
+
else
|
|
756
|
+
nil
|
|
757
|
+
end
|
|
758
|
+
end
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
# Generic entry insertion logic shared between RBTree and MultiRBTree.
|
|
763
|
+
#
|
|
764
|
+
# @param key [Object] the key to insert
|
|
765
|
+
# @yield [node, is_new] yields the existing node (if any) and whether it's a new insertion
|
|
766
|
+
# @yieldparam node [Node, nil] the existing node or nil
|
|
767
|
+
# @yieldparam is_new [Boolean] true if no node with the key exists
|
|
768
|
+
# @yieldreturn [Object]
|
|
769
|
+
# - if is_new: the initial value for the new node
|
|
770
|
+
# - if !is_new: the value to return from insert_entry
|
|
771
|
+
def insert_entry_generic(key)
|
|
669
772
|
if (node = @hash_index[key])
|
|
670
|
-
return
|
|
671
|
-
node.value = value
|
|
672
|
-
return true
|
|
773
|
+
return yield(node, false)
|
|
673
774
|
end
|
|
775
|
+
|
|
674
776
|
y = @nil_node
|
|
675
777
|
x = @root
|
|
676
778
|
while x != @nil_node
|
|
677
779
|
y = x
|
|
678
780
|
cmp = key <=> x.key
|
|
679
781
|
if cmp == 0
|
|
680
|
-
return
|
|
681
|
-
x.value = value
|
|
682
|
-
return true
|
|
782
|
+
return yield(x, false)
|
|
683
783
|
elsif cmp < 0
|
|
684
784
|
x = x.left
|
|
685
785
|
else
|
|
686
786
|
x = x.right
|
|
687
787
|
end
|
|
688
788
|
end
|
|
689
|
-
|
|
789
|
+
|
|
790
|
+
initial_value = yield(nil, true)
|
|
791
|
+
|
|
792
|
+
z = allocate_node(key, initial_value, Node::RED, @nil_node, @nil_node, @nil_node)
|
|
690
793
|
z.parent = y
|
|
691
794
|
if y == @nil_node
|
|
692
795
|
@root = z
|
|
@@ -703,14 +806,34 @@ class RBTree
|
|
|
703
806
|
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
704
807
|
@min_node = z
|
|
705
808
|
end
|
|
809
|
+
if @max_node == @nil_node || (key <=> @max_node.key) > 0
|
|
810
|
+
@max_node = z
|
|
811
|
+
end
|
|
706
812
|
|
|
707
|
-
@hash_index[key] = z
|
|
813
|
+
@hash_index[key] = z
|
|
708
814
|
true
|
|
709
815
|
end
|
|
710
816
|
|
|
817
|
+
# Traverses the tree in a specified direction (ascending or descending).
|
|
818
|
+
#
|
|
819
|
+
# @param direction [Boolean] true for descending, false for ascending
|
|
820
|
+
# @param min [Object] the lower bound (inclusive)
|
|
821
|
+
# @param max [Object] the upper bound (inclusive)
|
|
822
|
+
# @param include_min [Boolean] whether to include the lower bound
|
|
823
|
+
# @param include_max [Boolean] whether to include the upper bound
|
|
824
|
+
# @param safe [Boolean] whether to use safe traversal
|
|
825
|
+
# @yield [key, value] each key-value pair in the specified direction
|
|
826
|
+
# @return [void]
|
|
827
|
+
def traverse_range(direction, min, max, include_min, include_max, safe: false, &block)
|
|
828
|
+
if direction
|
|
829
|
+
traverse_range_desc(min, max, include_min, include_max, safe: safe, &block)
|
|
830
|
+
else
|
|
831
|
+
traverse_range_asc(min, max, include_min, include_max, safe: safe, &block)
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
|
|
711
835
|
# Traverses the tree in ascending order (in-order traversal).
|
|
712
836
|
#
|
|
713
|
-
# @param node [Node] the current node
|
|
714
837
|
# @param min [Object] the lower bound (inclusive)
|
|
715
838
|
# @param max [Object] the upper bound (inclusive)
|
|
716
839
|
# @param include_min [Boolean] whether to include the lower bound
|
|
@@ -718,56 +841,31 @@ class RBTree
|
|
|
718
841
|
# @param safe [Boolean] whether to use safe traversal
|
|
719
842
|
# @yield [key, value] each key-value pair in ascending order
|
|
720
843
|
# @return [void]
|
|
721
|
-
def traverse_range_asc(
|
|
844
|
+
def traverse_range_asc(min, max, include_min, include_max, safe: false, &block)
|
|
845
|
+
# O(1) Range Rejection
|
|
846
|
+
return if min && @max_node != @nil_node && (min <=> @max_node.key) > 0
|
|
847
|
+
return if min && @max_node != @nil_node && !include_min && (min <=> @max_node.key) == 0
|
|
848
|
+
|
|
849
|
+
# O(1) Bound Optimization
|
|
850
|
+
max = nil if max && @max_node != @nil_node && (cmp = max <=> @max_node.key) >= 0 && (cmp > 0 || include_max)
|
|
851
|
+
|
|
722
852
|
if safe
|
|
723
853
|
pair = !min ? find_min :
|
|
724
854
|
include_min && @hash_index[min]&.pair || find_successor(min)
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
pair = find_successor(current_key)
|
|
730
|
-
end
|
|
731
|
-
else
|
|
732
|
-
while pair && pair[0] < max
|
|
733
|
-
current_key = pair[0]
|
|
734
|
-
yield pair
|
|
735
|
-
pair = find_successor(current_key)
|
|
736
|
-
end
|
|
855
|
+
while pair && (!max || pair[0] < max)
|
|
856
|
+
current_key = pair[0]
|
|
857
|
+
yield pair
|
|
858
|
+
pair = find_successor(current_key)
|
|
737
859
|
end
|
|
738
860
|
yield pair if pair && max && include_max && pair[0] == max
|
|
739
861
|
else
|
|
740
|
-
stack =
|
|
741
|
-
|
|
742
|
-
while current != @nil_node || !stack.empty?
|
|
743
|
-
while current != @nil_node
|
|
744
|
-
if min && ((current.key <=> min) < 0 ||
|
|
745
|
-
(!include_min && (current.key <=> min) == 0))
|
|
746
|
-
current = current.right
|
|
747
|
-
else
|
|
748
|
-
stack << current
|
|
749
|
-
current = current.left
|
|
750
|
-
end
|
|
751
|
-
end
|
|
752
|
-
|
|
753
|
-
if !stack.empty?
|
|
754
|
-
current = stack.pop
|
|
755
|
-
|
|
756
|
-
if max && ((current.key <=> max) > 0 ||
|
|
757
|
-
(!include_max && (current.key <=> max) == 0))
|
|
758
|
-
return
|
|
759
|
-
else
|
|
760
|
-
yield current.pair
|
|
761
|
-
current = current.right
|
|
762
|
-
end
|
|
763
|
-
end
|
|
764
|
-
end
|
|
862
|
+
start_node, stack = resolve_startup_asc(min, include_min)
|
|
863
|
+
traverse_from_asc(start_node, stack, max, include_max, &block)
|
|
765
864
|
end
|
|
766
865
|
end
|
|
767
866
|
|
|
768
867
|
# Traverses the tree in descending order (reverse in-order traversal).
|
|
769
868
|
#
|
|
770
|
-
# @param node [Node] the current node
|
|
771
869
|
# @param min [Object] the lower bound (inclusive)
|
|
772
870
|
# @param max [Object] the upper bound (inclusive)
|
|
773
871
|
# @param include_min [Boolean] whether to include the lower bound
|
|
@@ -775,164 +873,218 @@ class RBTree
|
|
|
775
873
|
# @param safe [Boolean] whether to use safe traversal
|
|
776
874
|
# @yield [key, value] each key-value pair in descending order
|
|
777
875
|
# @return [void]
|
|
778
|
-
def traverse_range_desc(
|
|
876
|
+
def traverse_range_desc(min, max, include_min, include_max, safe: false, &block)
|
|
877
|
+
# O(1) Range Rejection
|
|
878
|
+
return if max && @min_node != @nil_node && (max <=> @min_node.key) < 0
|
|
879
|
+
return if max && @min_node != @nil_node && !include_max && (max <=> @min_node.key) == 0
|
|
880
|
+
|
|
881
|
+
# O(1) Bound Optimization
|
|
882
|
+
min = nil if min && @min_node != @nil_node && (cmp = min <=> @min_node.key) <= 0 && (cmp < 0 || include_min)
|
|
883
|
+
|
|
779
884
|
if safe
|
|
780
885
|
pair = !max ? find_max :
|
|
781
886
|
include_max && @hash_index[max]&.pair || find_predecessor(max)
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
pair = find_predecessor(current_key)
|
|
787
|
-
end
|
|
788
|
-
else
|
|
789
|
-
while pair && pair[0] > min
|
|
790
|
-
current_key = pair[0]
|
|
791
|
-
yield pair
|
|
792
|
-
pair = find_predecessor(current_key)
|
|
793
|
-
end
|
|
887
|
+
while pair && (!min || pair[0] > min)
|
|
888
|
+
current_key = pair[0]
|
|
889
|
+
yield pair
|
|
890
|
+
pair = find_predecessor(current_key)
|
|
794
891
|
end
|
|
795
892
|
yield pair if pair && min && include_min && pair[0] == min
|
|
796
893
|
else
|
|
797
|
-
stack =
|
|
798
|
-
|
|
799
|
-
while current != @nil_node || !stack.empty?
|
|
800
|
-
while current != @nil_node
|
|
801
|
-
if max && ((current.key <=> max) > 0 ||
|
|
802
|
-
(!include_max && (current.key <=> max) == 0))
|
|
803
|
-
current = current.left
|
|
804
|
-
else
|
|
805
|
-
stack << current
|
|
806
|
-
current = current.right
|
|
807
|
-
end
|
|
808
|
-
end
|
|
809
|
-
|
|
810
|
-
if !stack.empty?
|
|
811
|
-
current = stack.pop
|
|
812
|
-
|
|
813
|
-
if min && ((current.key <=> min) < 0 ||
|
|
814
|
-
(!include_min && (current.key <=> min) == 0))
|
|
815
|
-
return
|
|
816
|
-
else
|
|
817
|
-
yield current.pair
|
|
818
|
-
current = current.left
|
|
819
|
-
end
|
|
820
|
-
end
|
|
821
|
-
end
|
|
894
|
+
start_node, stack = resolve_startup_desc(max, include_max)
|
|
895
|
+
traverse_from_desc(start_node, stack, min, include_min, &block)
|
|
822
896
|
end
|
|
823
897
|
end
|
|
824
898
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
#
|
|
828
|
-
# @yield [key, value] each key-value pair in ascending order
|
|
829
|
-
# @return [void]
|
|
830
|
-
def traverse_all_asc(node, safe: false, &block) =
|
|
831
|
-
traverse_range_asc(node, nil, nil, false, false, safe: safe, &block)
|
|
832
|
-
|
|
833
|
-
# Traverses the tree in descending order (reverse in-order traversal).
|
|
834
|
-
#
|
|
835
|
-
# @param node [Node] the current node
|
|
836
|
-
# @yield [key, value] each key-value pair in descending order
|
|
837
|
-
# @return [void]
|
|
838
|
-
def traverse_all_desc(node, safe: false, &block) =
|
|
839
|
-
traverse_range_desc(node, nil, nil, false, false, safe: safe, &block)
|
|
840
|
-
|
|
841
|
-
# Traverses nodes with keys less than the specified key.
|
|
899
|
+
private
|
|
900
|
+
|
|
901
|
+
# Returns the predecessor of the given node.
|
|
842
902
|
#
|
|
843
|
-
# @param node [Node] the
|
|
844
|
-
# @
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
903
|
+
# @param node [Node] the node to find the predecessor of
|
|
904
|
+
# @return [Node] the predecessor node
|
|
905
|
+
def predecessor_node_of(node)
|
|
906
|
+
if node.left != @nil_node
|
|
907
|
+
return rightmost(node.left)
|
|
908
|
+
end
|
|
909
|
+
y = node.parent
|
|
910
|
+
while y != @nil_node && node == y.left
|
|
911
|
+
node = y
|
|
912
|
+
y = y.parent
|
|
913
|
+
end
|
|
914
|
+
y
|
|
915
|
+
end
|
|
849
916
|
|
|
850
|
-
#
|
|
917
|
+
# Returns the successor of the given node.
|
|
851
918
|
#
|
|
852
|
-
# @param node [Node] the
|
|
853
|
-
# @
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
919
|
+
# @param node [Node] the node to find the successor of
|
|
920
|
+
# @return [Node] the successor node
|
|
921
|
+
def successor_node_of(node)
|
|
922
|
+
if node.right != @nil_node
|
|
923
|
+
return leftmost(node.right)
|
|
924
|
+
end
|
|
925
|
+
y = node.parent
|
|
926
|
+
while y != @nil_node && node == y.right
|
|
927
|
+
node = y
|
|
928
|
+
y = y.parent
|
|
929
|
+
end
|
|
930
|
+
y
|
|
931
|
+
end
|
|
858
932
|
|
|
859
|
-
#
|
|
933
|
+
# Resolves the startup node for ascending traversal.
|
|
860
934
|
#
|
|
861
|
-
# @param
|
|
862
|
-
# @param
|
|
863
|
-
# @
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
935
|
+
# @param min [Object] the minimum key to traverse from
|
|
936
|
+
# @param include_min [Boolean] whether to include the minimum key
|
|
937
|
+
# @return [Array(Node, Array)] the starting node and stack
|
|
938
|
+
def resolve_startup_asc(min, include_min)
|
|
939
|
+
# 1. Use cached min if no lower bound or if min is less than tree min
|
|
940
|
+
if @min_node != @nil_node && (!min || (min <=> @min_node.key) < 0)
|
|
941
|
+
return [@min_node, reconstruct_stack_asc(@min_node)]
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
# 2. Use Hash index if key exists
|
|
945
|
+
if min && (node = @hash_index[min])
|
|
946
|
+
start_node = include_min ? node : successor_node_of(node)
|
|
947
|
+
return [start_node, reconstruct_stack_asc(start_node)]
|
|
948
|
+
end
|
|
867
949
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
950
|
+
# 3. Fallback to tree search from root
|
|
951
|
+
stack = []
|
|
952
|
+
current = @root
|
|
953
|
+
while current != @nil_node
|
|
954
|
+
if min && ((current.key <=> min) < 0 || (!include_min && (current.key <=> min) == 0))
|
|
955
|
+
current = current.right
|
|
956
|
+
else
|
|
957
|
+
stack << current
|
|
958
|
+
current = current.left
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
[@nil_node, stack]
|
|
962
|
+
end
|
|
876
963
|
|
|
877
|
-
#
|
|
878
|
-
#
|
|
879
|
-
# @param node [Node] the
|
|
880
|
-
# @
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
964
|
+
# Reconstructs the stack for ascending traversal.
|
|
965
|
+
#
|
|
966
|
+
# @param node [Node] the node to reconstruct the stack from
|
|
967
|
+
# @return [Array] the reconstructed stack
|
|
968
|
+
def reconstruct_stack_asc(node)
|
|
969
|
+
return [] if node == @nil_node
|
|
970
|
+
stack = []
|
|
971
|
+
curr = node
|
|
972
|
+
while (p = curr.parent) != @nil_node
|
|
973
|
+
stack << p if curr == p.left
|
|
974
|
+
curr = p
|
|
975
|
+
end
|
|
976
|
+
stack.reverse!
|
|
977
|
+
stack
|
|
978
|
+
end
|
|
888
979
|
|
|
889
|
-
# Traverses
|
|
980
|
+
# Traverses the tree in ascending order.
|
|
890
981
|
#
|
|
891
|
-
# @param
|
|
892
|
-
# @param
|
|
893
|
-
# @
|
|
894
|
-
# @
|
|
895
|
-
|
|
896
|
-
|
|
982
|
+
# @param current [Node] the current node
|
|
983
|
+
# @param stack [Array] the stack of nodes to traverse
|
|
984
|
+
# @param max [Object] the maximum key to traverse to
|
|
985
|
+
# @param include_max [Boolean] whether to include the maximum key
|
|
986
|
+
# @yield [Array(Object, Object)] each key-value pair
|
|
987
|
+
# @yieldparam key [Object] the key
|
|
988
|
+
# @yieldparam val [Object] the value
|
|
989
|
+
def traverse_from_asc(current, stack, max, include_max, &block)
|
|
990
|
+
while current != @nil_node || !stack.empty?
|
|
991
|
+
if current != @nil_node
|
|
992
|
+
if max
|
|
993
|
+
cmp = current.key <=> max
|
|
994
|
+
if cmp >= 0
|
|
995
|
+
yield current.pair if include_max && cmp == 0
|
|
996
|
+
return
|
|
997
|
+
end
|
|
998
|
+
end
|
|
999
|
+
yield current.pair
|
|
1000
|
+
current = current.right
|
|
1001
|
+
while current != @nil_node
|
|
1002
|
+
stack << current
|
|
1003
|
+
current = current.left
|
|
1004
|
+
end
|
|
1005
|
+
else
|
|
1006
|
+
current = stack.pop
|
|
1007
|
+
end
|
|
1008
|
+
end
|
|
1009
|
+
end
|
|
897
1010
|
|
|
898
|
-
#
|
|
1011
|
+
# Resolves the startup node for descending traversal.
|
|
899
1012
|
#
|
|
900
|
-
# @param
|
|
901
|
-
# @param
|
|
902
|
-
# @
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1013
|
+
# @param max [Object] the maximum key to traverse from
|
|
1014
|
+
# @param include_max [Boolean] whether to include the maximum key
|
|
1015
|
+
# @return [Array(Node, Array)] the starting node and stack
|
|
1016
|
+
def resolve_startup_desc(max, include_max)
|
|
1017
|
+
# 1. Use cached max if no upper bound or if max is greater than tree max
|
|
1018
|
+
if @max_node != @nil_node && (!max || (max <=> @max_node.key) > 0)
|
|
1019
|
+
return [@max_node, reconstruct_stack_desc(@max_node)]
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
# 2. Use Hash index if key exists
|
|
1023
|
+
if max && (node = @hash_index[max])
|
|
1024
|
+
start_node = include_max ? node : predecessor_node_of(node)
|
|
1025
|
+
return [start_node, reconstruct_stack_desc(start_node)]
|
|
1026
|
+
end
|
|
906
1027
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1028
|
+
# 3. Fallback to tree search from root
|
|
1029
|
+
stack = []
|
|
1030
|
+
current = @root
|
|
1031
|
+
while current != @nil_node
|
|
1032
|
+
if max && ((current.key <=> max) > 0 || (!include_max && (current.key <=> max) == 0))
|
|
1033
|
+
current = current.left
|
|
1034
|
+
else
|
|
1035
|
+
stack << current
|
|
1036
|
+
current = current.right
|
|
1037
|
+
end
|
|
1038
|
+
end
|
|
1039
|
+
[@nil_node, stack]
|
|
1040
|
+
end
|
|
915
1041
|
|
|
916
|
-
#
|
|
917
|
-
#
|
|
918
|
-
# @param node [Node] the
|
|
919
|
-
# @
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1042
|
+
# Reconstructs the stack for descending traversal.
|
|
1043
|
+
#
|
|
1044
|
+
# @param node [Node] the node to reconstruct the stack from
|
|
1045
|
+
# @return [Array] the reconstructed stack
|
|
1046
|
+
def reconstruct_stack_desc(node)
|
|
1047
|
+
return [] if node == @nil_node
|
|
1048
|
+
stack = []
|
|
1049
|
+
curr = node
|
|
1050
|
+
while (p = curr.parent) != @nil_node
|
|
1051
|
+
stack << p if curr == p.right
|
|
1052
|
+
curr = p
|
|
1053
|
+
end
|
|
1054
|
+
stack.reverse!
|
|
1055
|
+
stack
|
|
1056
|
+
end
|
|
924
1057
|
|
|
925
|
-
# Traverses
|
|
1058
|
+
# Traverses the tree in descending order.
|
|
926
1059
|
#
|
|
927
|
-
# @param
|
|
928
|
-
# @param
|
|
929
|
-
# @param
|
|
930
|
-
# @param include_min [Boolean] whether to include the
|
|
931
|
-
# @
|
|
932
|
-
# @
|
|
933
|
-
# @
|
|
934
|
-
def
|
|
935
|
-
|
|
1060
|
+
# @param current [Node] the current node
|
|
1061
|
+
# @param stack [Array] the stack of nodes to traverse
|
|
1062
|
+
# @param min [Object] the minimum key to traverse to
|
|
1063
|
+
# @param include_min [Boolean] whether to include the minimum key
|
|
1064
|
+
# @yield [Array(Object, Object)] each key-value pair
|
|
1065
|
+
# @yieldparam key [Object] the key
|
|
1066
|
+
# @yieldparam val [Object] the value
|
|
1067
|
+
def traverse_from_desc(current, stack, min, include_min, &block)
|
|
1068
|
+
while current != @nil_node || !stack.empty?
|
|
1069
|
+
if current != @nil_node
|
|
1070
|
+
if min
|
|
1071
|
+
cmp = current.key <=> min
|
|
1072
|
+
if cmp <= 0
|
|
1073
|
+
yield current.pair if include_min && cmp == 0
|
|
1074
|
+
return
|
|
1075
|
+
end
|
|
1076
|
+
end
|
|
1077
|
+
yield current.pair
|
|
1078
|
+
current = current.left
|
|
1079
|
+
while current != @nil_node
|
|
1080
|
+
stack << current
|
|
1081
|
+
current = current.right
|
|
1082
|
+
end
|
|
1083
|
+
else
|
|
1084
|
+
current = stack.pop
|
|
1085
|
+
end
|
|
1086
|
+
end
|
|
1087
|
+
end
|
|
936
1088
|
|
|
937
1089
|
# Restores red-black tree properties after insertion.
|
|
938
1090
|
#
|
|
@@ -1036,6 +1188,10 @@ class RBTree
|
|
|
1036
1188
|
if next_min_node
|
|
1037
1189
|
@min_node = next_min_node
|
|
1038
1190
|
end
|
|
1191
|
+
if z == @max_node
|
|
1192
|
+
next_max_node = predecessor_node_of(z)
|
|
1193
|
+
@max_node = next_max_node
|
|
1194
|
+
end
|
|
1039
1195
|
|
|
1040
1196
|
value = z.value
|
|
1041
1197
|
release_node(z)
|
|
@@ -1128,6 +1284,13 @@ class RBTree
|
|
|
1128
1284
|
# @param key [Numeric] the target key
|
|
1129
1285
|
# @return [Node] the nearest node, or @nil_node if tree is empty
|
|
1130
1286
|
def find_nearest_node(key)
|
|
1287
|
+
raise ArgumentError, "key must be Numeric" unless key.is_a?(Numeric)
|
|
1288
|
+
|
|
1289
|
+
# If key is larger than max_key, return max_node
|
|
1290
|
+
return @max_node if @max_node != @nil_node && key >= @max_node.key
|
|
1291
|
+
# If key is smaller than min_key, return min_node
|
|
1292
|
+
return @min_node if @min_node != @nil_node && key <= @min_node.key
|
|
1293
|
+
|
|
1131
1294
|
current = @root
|
|
1132
1295
|
closest = @nil_node
|
|
1133
1296
|
min_dist = nil
|
|
@@ -1168,21 +1331,11 @@ class RBTree
|
|
|
1168
1331
|
# @param key [Object] the reference key
|
|
1169
1332
|
# @return [Node] the predecessor node, or @nil_node if none exists
|
|
1170
1333
|
def find_predecessor_node(key)
|
|
1171
|
-
#
|
|
1334
|
+
# If key is larger than max_key, return max_node
|
|
1335
|
+
return @max_node if max_key && (key <=> max_key) > 0
|
|
1336
|
+
# If key exists using O(1) hash lookup, return predecessor node
|
|
1172
1337
|
if (node = @hash_index[key])
|
|
1173
|
-
|
|
1174
|
-
if node.left != @nil_node
|
|
1175
|
-
return rightmost(node.left)
|
|
1176
|
-
else
|
|
1177
|
-
# Walk up to find first ancestor where we came from the right
|
|
1178
|
-
current = node
|
|
1179
|
-
parent = current.parent
|
|
1180
|
-
while parent != @nil_node && current == parent.left
|
|
1181
|
-
current = parent
|
|
1182
|
-
parent = parent.parent
|
|
1183
|
-
end
|
|
1184
|
-
return parent
|
|
1185
|
-
end
|
|
1338
|
+
return predecessor_node_of(node)
|
|
1186
1339
|
end
|
|
1187
1340
|
|
|
1188
1341
|
# Key doesn't exist: descend tree tracking the best candidate
|
|
@@ -1218,25 +1371,14 @@ class RBTree
|
|
|
1218
1371
|
# @param key [Object] the reference key
|
|
1219
1372
|
# @return [Node] the successor node, or @nil_node if none exists
|
|
1220
1373
|
def find_successor_node(key)
|
|
1374
|
+
# If key is larger than or equal to max_key, return nil
|
|
1375
|
+
return @nil_node if max_key && (key <=> max_key) >= 0
|
|
1221
1376
|
# If key is smaller than min_key, return min_node
|
|
1222
|
-
return @min_node if min_key && key <
|
|
1223
|
-
#
|
|
1377
|
+
return @min_node if min_key && (key <=> min_key) < 0
|
|
1378
|
+
# If key exists using O(1) hash lookup, return successor node
|
|
1224
1379
|
if (node = @hash_index[key])
|
|
1225
|
-
|
|
1226
|
-
if node.right != @nil_node
|
|
1227
|
-
return leftmost(node.right)
|
|
1228
|
-
else
|
|
1229
|
-
# Walk up to find first ancestor where we came from the left
|
|
1230
|
-
current = node
|
|
1231
|
-
parent = current.parent
|
|
1232
|
-
while parent != @nil_node && current == parent.right
|
|
1233
|
-
current = parent
|
|
1234
|
-
parent = parent.parent
|
|
1235
|
-
end
|
|
1236
|
-
return parent
|
|
1237
|
-
end
|
|
1380
|
+
return successor_node_of(node)
|
|
1238
1381
|
end
|
|
1239
|
-
|
|
1240
1382
|
# Key doesn't exist: descend tree tracking the best candidate
|
|
1241
1383
|
current = @root
|
|
1242
1384
|
successor = @nil_node
|
|
@@ -1292,7 +1434,7 @@ class RBTree
|
|
|
1292
1434
|
# Returns the maximum key-value pair.
|
|
1293
1435
|
#
|
|
1294
1436
|
# @return [Array(Object, Object), nil] a two-element array [key, value], or nil if tree is empty
|
|
1295
|
-
def find_max = ((n =
|
|
1437
|
+
def find_max = ((n = @max_node) != @nil_node) && n.pair
|
|
1296
1438
|
|
|
1297
1439
|
# Performs a left rotation on the given node.
|
|
1298
1440
|
#
|
|
@@ -1477,6 +1619,13 @@ class MultiRBTree < RBTree
|
|
|
1477
1619
|
# Returns the number of values stored in the tree.
|
|
1478
1620
|
# @return [Integer] the number of values in the tree
|
|
1479
1621
|
def size = @value_count
|
|
1622
|
+
|
|
1623
|
+
# Removes all elements from the tree.
|
|
1624
|
+
# @return [MultiRBTree] self
|
|
1625
|
+
def clear
|
|
1626
|
+
@value_count = 0
|
|
1627
|
+
super
|
|
1628
|
+
end
|
|
1480
1629
|
|
|
1481
1630
|
# Returns the minimum key-value pair without removing it.
|
|
1482
1631
|
#
|
|
@@ -1679,9 +1828,46 @@ class MultiRBTree < RBTree
|
|
|
1679
1828
|
[key, val]
|
|
1680
1829
|
end
|
|
1681
1830
|
|
|
1831
|
+
# Keeps key-value pairs for which the block returns true, deleting the rest.
|
|
1832
|
+
# Unlike RBTree, this removes individual values rather than entire keys.
|
|
1833
|
+
#
|
|
1834
|
+
# @yield [key, value] each key-value pair
|
|
1835
|
+
# @return [MultiRBTree, Enumerator] self, or Enumerator if no block
|
|
1836
|
+
def keep_if(&block)
|
|
1837
|
+
return enum_for(__method__) { size } unless block_given?
|
|
1838
|
+
filter_values! { |k, v| block.call(k, v) }
|
|
1839
|
+
self
|
|
1840
|
+
end
|
|
1841
|
+
|
|
1842
|
+
# Deletes key-value pairs for which the block returns true.
|
|
1843
|
+
# Unlike RBTree, this removes individual values rather than entire keys.
|
|
1844
|
+
#
|
|
1845
|
+
# @yield [key, value] each key-value pair
|
|
1846
|
+
# @return [MultiRBTree, Enumerator] self, or Enumerator if no block
|
|
1847
|
+
def delete_if(&block)
|
|
1848
|
+
return enum_for(__method__) { size } unless block_given?
|
|
1849
|
+
filter_values! { |k, v| !block.call(k, v) }
|
|
1850
|
+
self
|
|
1851
|
+
end
|
|
1852
|
+
|
|
1682
1853
|
# @!visibility private
|
|
1683
1854
|
private
|
|
1684
1855
|
|
|
1856
|
+
# Filters values in-place across all nodes.
|
|
1857
|
+
# Keeps only values for which the block returns true.
|
|
1858
|
+
# Removes nodes whose value arrays become empty.
|
|
1859
|
+
# Updates @value_count accordingly.
|
|
1860
|
+
def filter_values!
|
|
1861
|
+
keys_to_delete = []
|
|
1862
|
+
@hash_index.each do |key, node|
|
|
1863
|
+
before = node.value.size
|
|
1864
|
+
node.value.select! { |v| yield key, v }
|
|
1865
|
+
@value_count -= before - node.value.size
|
|
1866
|
+
keys_to_delete << key if node.value.empty?
|
|
1867
|
+
end
|
|
1868
|
+
keys_to_delete.each { |k| delete_indexed_node(k) }
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1685
1871
|
# Inserts a value for the given key.
|
|
1686
1872
|
#
|
|
1687
1873
|
# If the key already exists, the value is appended to its list.
|
|
@@ -1696,47 +1882,15 @@ class MultiRBTree < RBTree
|
|
|
1696
1882
|
# tree.insert(1, 'first')
|
|
1697
1883
|
# tree.insert(1, 'second') # adds another value for key 1
|
|
1698
1884
|
def insert_entry(key, value, **)
|
|
1699
|
-
|
|
1700
|
-
node.value << value
|
|
1885
|
+
insert_entry_generic(key) do |node, is_new|
|
|
1701
1886
|
@value_count += 1
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
y = @nil_node
|
|
1705
|
-
x = @root
|
|
1706
|
-
while x != @nil_node
|
|
1707
|
-
y = x
|
|
1708
|
-
cmp = key <=> x.key
|
|
1709
|
-
if cmp == 0
|
|
1710
|
-
x.value << value
|
|
1711
|
-
@value_count += 1
|
|
1712
|
-
return true
|
|
1713
|
-
elsif cmp < 0
|
|
1714
|
-
x = x.left
|
|
1887
|
+
if is_new
|
|
1888
|
+
[value]
|
|
1715
1889
|
else
|
|
1716
|
-
|
|
1890
|
+
node.value << value
|
|
1891
|
+
true
|
|
1717
1892
|
end
|
|
1718
1893
|
end
|
|
1719
|
-
z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
|
|
1720
|
-
z.parent = y
|
|
1721
|
-
if y == @nil_node
|
|
1722
|
-
@root = z
|
|
1723
|
-
elsif (key <=> y.key) < 0
|
|
1724
|
-
y.left = z
|
|
1725
|
-
else
|
|
1726
|
-
y.right = z
|
|
1727
|
-
end
|
|
1728
|
-
z.left = @nil_node
|
|
1729
|
-
z.right = @nil_node
|
|
1730
|
-
z.color = Node::RED
|
|
1731
|
-
insert_fixup(z)
|
|
1732
|
-
@value_count += 1
|
|
1733
|
-
|
|
1734
|
-
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
1735
|
-
@min_node = z
|
|
1736
|
-
end
|
|
1737
|
-
|
|
1738
|
-
@hash_index[key] = z # Add to hash index
|
|
1739
|
-
true
|
|
1740
1894
|
end
|
|
1741
1895
|
|
|
1742
1896
|
# Traverses the tree in ascending order, yielding each key-value pair.
|