rbtree-ruby 0.1.6 → 0.1.7
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 +25 -0
- data/README.md +71 -10
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +437 -41
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8aa1d883925f6b7f608108f1f916598aa04be013aee415c06dfc7bda35d6986c
|
|
4
|
+
data.tar.gz: e935f213100c933b42a3cf0d709809d5ca8d536db56d6e27a6b3de9681847ad9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 419f0270137b6a6ae39bd73d5ea0c4cc25ff0769c288619ee795d172e724040c6032c09c952745b9bb871ff082861c00ae879fa6a98a571414bad87c2534cb9a
|
|
7
|
+
data.tar.gz: e403cc18d16ff9661a341fb326b3aadebc5d1a83b69ed03395b318cf6666d8de9dca24e88da2a9c231395aa7780ce57ed89902717ad1266bd1551e384872719f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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.1.7] - 2026-01-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Predecessor/Successor Search**: New `prev(key)` and `succ(key)` methods
|
|
12
|
+
- Returns the key-value pair immediately before/after the given key
|
|
13
|
+
- Works even if the key doesn't exist in the tree (returns nearest neighbor)
|
|
14
|
+
- Uses hash index for O(1) existence check, then O(log n) tree traversal
|
|
15
|
+
- **Reverse Range Queries**: All range query methods now support `:reverse` option
|
|
16
|
+
- `lt(key, reverse: true)`, `lte`, `gt`, `gte`, `between`
|
|
17
|
+
- Returns results in descending order instead of ascending
|
|
18
|
+
- **MultiRBTree Enhancements**:
|
|
19
|
+
- `get(key, last: false)` - Choose first or last value from array
|
|
20
|
+
- `get_first(key)`, `get_last(key)` - Convenient aliases
|
|
21
|
+
- `delete_one(key, last: false)` - Choose which end to delete from
|
|
22
|
+
- `delete_first(key)`, `delete_last(key)` - Convenient aliases
|
|
23
|
+
- `min(last: false)`, `max(last: false)` - Choose first or last value
|
|
24
|
+
- `prev(key, last: false)`, `succ(key, last: false)` - Choose first or last value
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **MultiRBTree Iteration**: Reverse iteration (`reverse_each`, `lt(..., reverse: true)`, etc.) now iterates each key's value array in reverse order (last to first), making it a true mirror of forward iteration
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- **MultiRBTree size tracking**: Fixed bug where `insert` did not increment size when key already existed in hash index
|
|
31
|
+
|
|
8
32
|
## [0.1.6] - 2026-01-13
|
|
9
33
|
|
|
10
34
|
### Changed
|
|
@@ -83,6 +107,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
83
107
|
- ASCII diagrams for tree rotation operations
|
|
84
108
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
85
109
|
|
|
110
|
+
[0.1.7]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.7
|
|
86
111
|
[0.1.6]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.6
|
|
87
112
|
[0.1.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.5
|
|
88
113
|
[0.1.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.4
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# rbtree-ruby
|
|
2
2
|
|
|
3
3
|
A pure Ruby implementation of the Red-Black Tree data structure, providing efficient ordered key-value storage with O(log n) time complexity for insertion, deletion, and lookup operations.
|
|
4
4
|
|
|
@@ -127,6 +127,66 @@ tree.nearest(7) # => [5, "five"] (same distance, returns smaller key)
|
|
|
127
127
|
tree.nearest(8) # => [10, "ten"]
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
### Predecessor/Successor Search
|
|
131
|
+
|
|
132
|
+
Find the next or previous key in the tree:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
|
|
136
|
+
|
|
137
|
+
tree.prev(5) # => [3, "three"] (largest key < 5)
|
|
138
|
+
tree.succ(5) # => [7, "seven"] (smallest key > 5)
|
|
139
|
+
|
|
140
|
+
# Works even if the key doesn't exist
|
|
141
|
+
tree.prev(4) # => [3, "three"] (4 doesn't exist, returns largest key < 4)
|
|
142
|
+
tree.succ(4) # => [5, "five"] (4 doesn't exist, returns smallest key > 4)
|
|
143
|
+
|
|
144
|
+
# Returns nil at boundaries
|
|
145
|
+
tree.prev(1) # => nil (no key smaller than 1)
|
|
146
|
+
tree.succ(7) # => nil (no key larger than 7)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Reverse Range Queries
|
|
150
|
+
|
|
151
|
+
All range queries support a `:reverse` option to iterate in descending order:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
155
|
+
|
|
156
|
+
tree.lt(3) # => [[1, "one"], [2, "two"]]
|
|
157
|
+
tree.lt(3, reverse: true) # => [[2, "two"], [1, "one"]]
|
|
158
|
+
|
|
159
|
+
tree.between(1, 4, reverse: true) # => [[4, "four"], [3, "three"], [2, "two"], [1, "one"]]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### MultiRBTree Value Array Access
|
|
163
|
+
|
|
164
|
+
For keys with multiple values, choose which value to access:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
tree = MultiRBTree.new
|
|
168
|
+
tree.insert(1, 'first')
|
|
169
|
+
tree.insert(1, 'second')
|
|
170
|
+
tree.insert(1, 'third')
|
|
171
|
+
|
|
172
|
+
# Access first or last value
|
|
173
|
+
tree.get(1) # => "first"
|
|
174
|
+
tree.get(1, last: true) # => "third"
|
|
175
|
+
tree.get_first(1) # => "first"
|
|
176
|
+
tree.get_last(1) # => "third"
|
|
177
|
+
|
|
178
|
+
# Delete from either end
|
|
179
|
+
tree.delete_first(1) # => "first"
|
|
180
|
+
tree.delete_last(1) # => "third"
|
|
181
|
+
tree.get(1) # => "second"
|
|
182
|
+
|
|
183
|
+
# min/max with :last option
|
|
184
|
+
tree.insert(2, 'a')
|
|
185
|
+
tree.insert(2, 'b')
|
|
186
|
+
tree.min # => [1, "second"] (first value of min key)
|
|
187
|
+
tree.max(last: true) # => [2, "b"] (last value of max key)
|
|
188
|
+
```
|
|
189
|
+
|
|
130
190
|
## Performance
|
|
131
191
|
|
|
132
192
|
All major operations run in **O(log n)** time:
|
|
@@ -138,6 +198,7 @@ All major operations run in **O(log n)** time:
|
|
|
138
198
|
- `min` - **O(1)**
|
|
139
199
|
- `max` - O(log n)
|
|
140
200
|
- `shift` / `pop` - O(log n)
|
|
201
|
+
- `prev` / `succ` - O(log n) with O(1) hash check
|
|
141
202
|
|
|
142
203
|
Iteration over all elements takes O(n) time.
|
|
143
204
|
|
|
@@ -147,17 +208,17 @@ RBTree uses an internal **Memory Pool** to recycle node objects.
|
|
|
147
208
|
- Significantly reduces Garbage Collection (GC) pressure during frequent insertions and deletions (e.g., in high-throughput queues).
|
|
148
209
|
- In benchmarks with 100,000 cyclic operations, **GC time was 0.0s** compared to significant pauses without pooling.
|
|
149
210
|
|
|
150
|
-
### RBTree vs Hash vs Array
|
|
211
|
+
### RBTree vs Hash vs Array (Overwhelming Power)
|
|
151
212
|
|
|
152
|
-
RBTree
|
|
213
|
+
For ordered and spatial operations, RBTree is not just faster—it is in a completely different class. The following benchmarks were conducted with **500,000 items**:
|
|
153
214
|
|
|
154
|
-
| Operation | RBTree | Hash | Speedup |
|
|
155
|
-
|
|
156
|
-
|
|
|
157
|
-
| Range
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
| Key
|
|
215
|
+
| Operation | RBTree | Hash/Array | Speedup | Why? |
|
|
216
|
+
|-----------|--------|------------|---------|------|
|
|
217
|
+
| **Nearest Key Search** | **O(log n)** | O(n) scan | **~8,600x faster** | Spatial binary search vs full scan |
|
|
218
|
+
| **Range Queries** | **O(log n + k)** | O(n) filter | **~540x faster** | Direct subtree jump vs full scan |
|
|
219
|
+
| **Min Extraction** | **O(log n)** | O(n) search | **~160x faster** | Continuous rebalancing vs full scan |
|
|
220
|
+
| **Sorted Iteration** | **O(n)** | O(n log n) | **FREE** | Always sorted vs explicit `sort` |
|
|
221
|
+
| **Key Lookup** | **O(1)** | O(1) | **Equal** | Optimized Hybrid Hash Index |
|
|
161
222
|
|
|
162
223
|
### When to Use RBTree
|
|
163
224
|
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -329,19 +329,28 @@ class RBTree
|
|
|
329
329
|
# Retrieves all key-value pairs with keys less than the specified key.
|
|
330
330
|
#
|
|
331
331
|
# @param key [Object] the upper bound (exclusive)
|
|
332
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
332
333
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
333
334
|
# @return [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
|
|
334
335
|
# @example
|
|
335
336
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
336
337
|
# tree.lt(3) # => [[1, "one"], [2, "two"]]
|
|
337
|
-
# tree.lt(3
|
|
338
|
-
def lt(key, &block)
|
|
338
|
+
# tree.lt(3, reverse: true) # => [[2, "two"], [1, "one"]]
|
|
339
|
+
def lt(key, reverse: false, &block)
|
|
339
340
|
if block_given?
|
|
340
|
-
|
|
341
|
+
if reverse
|
|
342
|
+
traverse_lt_desc(@root, key, &block)
|
|
343
|
+
else
|
|
344
|
+
traverse_lt(@root, key, &block)
|
|
345
|
+
end
|
|
341
346
|
self
|
|
342
347
|
else
|
|
343
348
|
res = []
|
|
344
|
-
|
|
349
|
+
if reverse
|
|
350
|
+
traverse_lt_desc(@root, key) { |k, v| res << [k, v] }
|
|
351
|
+
else
|
|
352
|
+
traverse_lt(@root, key) { |k, v| res << [k, v] }
|
|
353
|
+
end
|
|
345
354
|
res
|
|
346
355
|
end
|
|
347
356
|
end
|
|
@@ -349,18 +358,28 @@ class RBTree
|
|
|
349
358
|
# Retrieves all key-value pairs with keys less than or equal to the specified key.
|
|
350
359
|
#
|
|
351
360
|
# @param key [Object] the upper bound (inclusive)
|
|
361
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
352
362
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
353
363
|
# @return [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
|
|
354
364
|
# @example
|
|
355
365
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
356
366
|
# tree.lte(3) # => [[1, "one"], [2, "two"], [3, "three"]]
|
|
357
|
-
|
|
367
|
+
# tree.lte(3, reverse: true) # => [[3, "three"], [2, "two"], [1, "one"]]
|
|
368
|
+
def lte(key, reverse: false, &block)
|
|
358
369
|
if block_given?
|
|
359
|
-
|
|
370
|
+
if reverse
|
|
371
|
+
traverse_lte_desc(@root, key, &block)
|
|
372
|
+
else
|
|
373
|
+
traverse_lte(@root, key, &block)
|
|
374
|
+
end
|
|
360
375
|
self
|
|
361
376
|
else
|
|
362
377
|
res = []
|
|
363
|
-
|
|
378
|
+
if reverse
|
|
379
|
+
traverse_lte_desc(@root, key) { |k, v| res << [k, v] }
|
|
380
|
+
else
|
|
381
|
+
traverse_lte(@root, key) { |k, v| res << [k, v] }
|
|
382
|
+
end
|
|
364
383
|
res
|
|
365
384
|
end
|
|
366
385
|
end
|
|
@@ -368,18 +387,28 @@ class RBTree
|
|
|
368
387
|
# Retrieves all key-value pairs with keys greater than the specified key.
|
|
369
388
|
#
|
|
370
389
|
# @param key [Object] the lower bound (exclusive)
|
|
390
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
371
391
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
372
392
|
# @return [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
|
|
373
393
|
# @example
|
|
374
394
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
375
395
|
# tree.gt(2) # => [[3, "three"], [4, "four"]]
|
|
376
|
-
|
|
396
|
+
# tree.gt(2, reverse: true) # => [[4, "four"], [3, "three"]]
|
|
397
|
+
def gt(key, reverse: false, &block)
|
|
377
398
|
if block_given?
|
|
378
|
-
|
|
399
|
+
if reverse
|
|
400
|
+
traverse_gt_desc(@root, key, &block)
|
|
401
|
+
else
|
|
402
|
+
traverse_gt(@root, key, &block)
|
|
403
|
+
end
|
|
379
404
|
self
|
|
380
405
|
else
|
|
381
406
|
res = []
|
|
382
|
-
|
|
407
|
+
if reverse
|
|
408
|
+
traverse_gt_desc(@root, key) { |k, v| res << [k, v] }
|
|
409
|
+
else
|
|
410
|
+
traverse_gt(@root, key) { |k, v| res << [k, v] }
|
|
411
|
+
end
|
|
383
412
|
res
|
|
384
413
|
end
|
|
385
414
|
end
|
|
@@ -387,18 +416,28 @@ class RBTree
|
|
|
387
416
|
# Retrieves all key-value pairs with keys greater than or equal to the specified key.
|
|
388
417
|
#
|
|
389
418
|
# @param key [Object] the lower bound (inclusive)
|
|
419
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
390
420
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
391
421
|
# @return [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
|
|
392
422
|
# @example
|
|
393
423
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
394
424
|
# tree.gte(2) # => [[2, "two"], [3, "three"], [4, "four"]]
|
|
395
|
-
|
|
425
|
+
# tree.gte(2, reverse: true) # => [[4, "four"], [3, "three"], [2, "two"]]
|
|
426
|
+
def gte(key, reverse: false, &block)
|
|
396
427
|
if block_given?
|
|
397
|
-
|
|
428
|
+
if reverse
|
|
429
|
+
traverse_gte_desc(@root, key, &block)
|
|
430
|
+
else
|
|
431
|
+
traverse_gte(@root, key, &block)
|
|
432
|
+
end
|
|
398
433
|
self
|
|
399
434
|
else
|
|
400
435
|
res = []
|
|
401
|
-
|
|
436
|
+
if reverse
|
|
437
|
+
traverse_gte_desc(@root, key) { |k, v| res << [k, v] }
|
|
438
|
+
else
|
|
439
|
+
traverse_gte(@root, key) { |k, v| res << [k, v] }
|
|
440
|
+
end
|
|
402
441
|
res
|
|
403
442
|
end
|
|
404
443
|
end
|
|
@@ -409,20 +448,29 @@ class RBTree
|
|
|
409
448
|
# @param max [Object] the upper bound
|
|
410
449
|
# @param include_min [Boolean] whether to include the lower bound (default: true)
|
|
411
450
|
# @param include_max [Boolean] whether to include the upper bound (default: true)
|
|
451
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
412
452
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
413
453
|
# @return [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
|
|
414
454
|
# @example
|
|
415
455
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five'})
|
|
416
456
|
# tree.between(2, 4) # => [[2, "two"], [3, "three"], [4, "four"]]
|
|
417
457
|
# tree.between(2, 4, include_min: false) # => [[3, "three"], [4, "four"]]
|
|
418
|
-
# tree.between(2, 4,
|
|
419
|
-
def between(min, max, include_min: true, include_max: true, &block)
|
|
458
|
+
# tree.between(2, 4, reverse: true) # => [[4, "four"], [3, "three"], [2, "two"]]
|
|
459
|
+
def between(min, max, include_min: true, include_max: true, reverse: false, &block)
|
|
420
460
|
if block_given?
|
|
421
|
-
|
|
461
|
+
if reverse
|
|
462
|
+
traverse_between_desc(@root, min, max, include_min, include_max, &block)
|
|
463
|
+
else
|
|
464
|
+
traverse_between(@root, min, max, include_min, include_max, &block)
|
|
465
|
+
end
|
|
422
466
|
self
|
|
423
467
|
else
|
|
424
468
|
res = []
|
|
425
|
-
|
|
469
|
+
if reverse
|
|
470
|
+
traverse_between_desc(@root, min, max, include_min, include_max) { |k, v| res << [k, v] }
|
|
471
|
+
else
|
|
472
|
+
traverse_between(@root, min, max, include_min, include_max) { |k, v| res << [k, v] }
|
|
473
|
+
end
|
|
426
474
|
res
|
|
427
475
|
end
|
|
428
476
|
end
|
|
@@ -445,6 +493,40 @@ class RBTree
|
|
|
445
493
|
n == @nil_node ? nil : [n.key, n.value]
|
|
446
494
|
end
|
|
447
495
|
|
|
496
|
+
# Returns the key-value pair with the largest key that is smaller than the given key.
|
|
497
|
+
#
|
|
498
|
+
# If the key exists in the tree, returns the predecessor (previous element).
|
|
499
|
+
# If the key does not exist, returns the largest key-value pair with key < given key.
|
|
500
|
+
#
|
|
501
|
+
# @param key [Object] the reference key
|
|
502
|
+
# @return [Array(Object, Object), nil] a two-element array [key, value], or nil if no predecessor exists
|
|
503
|
+
# @example
|
|
504
|
+
# tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
|
|
505
|
+
# tree.prev(5) # => [3, "three"]
|
|
506
|
+
# tree.prev(4) # => [3, "three"] (4 does not exist)
|
|
507
|
+
# tree.prev(1) # => nil (no predecessor)
|
|
508
|
+
def prev(key)
|
|
509
|
+
n = find_predecessor_node(key)
|
|
510
|
+
n == @nil_node ? nil : [n.key, n.value]
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Returns the key-value pair with the smallest key that is larger than the given key.
|
|
514
|
+
#
|
|
515
|
+
# If the key exists in the tree, returns the successor (next element).
|
|
516
|
+
# If the key does not exist, returns the smallest key-value pair with key > given key.
|
|
517
|
+
#
|
|
518
|
+
# @param key [Object] the reference key
|
|
519
|
+
# @return [Array(Object, Object), nil] a two-element array [key, value], or nil if no successor exists
|
|
520
|
+
# @example
|
|
521
|
+
# tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
|
|
522
|
+
# tree.succ(5) # => [7, "seven"]
|
|
523
|
+
# tree.succ(4) # => [5, "five"] (4 does not exist)
|
|
524
|
+
# tree.succ(7) # => nil (no successor)
|
|
525
|
+
def succ(key)
|
|
526
|
+
n = find_successor_node(key)
|
|
527
|
+
n == @nil_node ? nil : [n.key, n.value]
|
|
528
|
+
end
|
|
529
|
+
|
|
448
530
|
# Validates the red-black tree properties.
|
|
449
531
|
#
|
|
450
532
|
# Checks that:
|
|
@@ -593,6 +675,98 @@ class RBTree
|
|
|
593
675
|
end
|
|
594
676
|
end
|
|
595
677
|
|
|
678
|
+
# Traverses nodes with keys less than the specified key in descending order.
|
|
679
|
+
#
|
|
680
|
+
# @param node [Node] the current node
|
|
681
|
+
# @param key [Object] the upper bound (exclusive)
|
|
682
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
683
|
+
# @return [void]
|
|
684
|
+
def traverse_lt_desc(node, key, &block)
|
|
685
|
+
return if node == @nil_node
|
|
686
|
+
|
|
687
|
+
if (node.key <=> key) < 0
|
|
688
|
+
traverse_lt_desc(node.right, key, &block)
|
|
689
|
+
yield node.key, node.value
|
|
690
|
+
end
|
|
691
|
+
traverse_lt_desc(node.left, key, &block)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
# Traverses nodes with keys less than or equal to the specified key in descending order.
|
|
695
|
+
#
|
|
696
|
+
# @param node [Node] the current node
|
|
697
|
+
# @param key [Object] the upper bound (inclusive)
|
|
698
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
699
|
+
# @return [void]
|
|
700
|
+
def traverse_lte_desc(node, key, &block)
|
|
701
|
+
return if node == @nil_node
|
|
702
|
+
|
|
703
|
+
if (node.key <=> key) <= 0
|
|
704
|
+
traverse_lte_desc(node.right, key, &block)
|
|
705
|
+
yield node.key, node.value
|
|
706
|
+
end
|
|
707
|
+
traverse_lte_desc(node.left, key, &block)
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Traverses nodes with keys greater than the specified key in descending order.
|
|
711
|
+
#
|
|
712
|
+
# @param node [Node] the current node
|
|
713
|
+
# @param key [Object] the lower bound (exclusive)
|
|
714
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
715
|
+
# @return [void]
|
|
716
|
+
def traverse_gt_desc(node, key, &block)
|
|
717
|
+
return if node == @nil_node
|
|
718
|
+
|
|
719
|
+
traverse_gt_desc(node.right, key, &block)
|
|
720
|
+
if (node.key <=> key) > 0
|
|
721
|
+
yield node.key, node.value
|
|
722
|
+
traverse_gt_desc(node.left, key, &block)
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# Traverses nodes with keys greater than or equal to the specified key in descending order.
|
|
727
|
+
#
|
|
728
|
+
# @param node [Node] the current node
|
|
729
|
+
# @param key [Object] the lower bound (inclusive)
|
|
730
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
731
|
+
# @return [void]
|
|
732
|
+
def traverse_gte_desc(node, key, &block)
|
|
733
|
+
return if node == @nil_node
|
|
734
|
+
|
|
735
|
+
traverse_gte_desc(node.right, key, &block)
|
|
736
|
+
if (node.key <=> key) >= 0
|
|
737
|
+
yield node.key, node.value
|
|
738
|
+
traverse_gte_desc(node.left, key, &block)
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Traverses nodes with keys within the specified range in descending order.
|
|
743
|
+
#
|
|
744
|
+
# @param node [Node] the current node
|
|
745
|
+
# @param min [Object] the lower bound
|
|
746
|
+
# @param max [Object] the upper bound
|
|
747
|
+
# @param include_min [Boolean] whether to include the lower bound
|
|
748
|
+
# @param include_max [Boolean] whether to include the upper bound
|
|
749
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
750
|
+
# @return [void]
|
|
751
|
+
def traverse_between_desc(node, min, max, include_min, include_max, &block)
|
|
752
|
+
return if node == @nil_node
|
|
753
|
+
|
|
754
|
+
if (node.key <=> max) < 0
|
|
755
|
+
traverse_between_desc(node.right, min, max, include_min, include_max, &block)
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
greater = include_min ? (node.key <=> min) >= 0 : (node.key <=> min) > 0
|
|
759
|
+
less = include_max ? (node.key <=> max) <= 0 : (node.key <=> max) < 0
|
|
760
|
+
|
|
761
|
+
if greater && less
|
|
762
|
+
yield node.key, node.value
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
if (node.key <=> min) > 0
|
|
766
|
+
traverse_between_desc(node.left, min, max, include_min, include_max, &block)
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
|
|
596
770
|
# Restores red-black tree properties after insertion.
|
|
597
771
|
#
|
|
598
772
|
# This method fixes any violations of red-black properties that may occur
|
|
@@ -842,6 +1016,86 @@ class RBTree
|
|
|
842
1016
|
closest
|
|
843
1017
|
end
|
|
844
1018
|
|
|
1019
|
+
# Finds the node with the largest key that is smaller than the given key.
|
|
1020
|
+
#
|
|
1021
|
+
# If the key exists in the tree, returns its predecessor node.
|
|
1022
|
+
# If the key does not exist, returns the largest node with key < given key.
|
|
1023
|
+
#
|
|
1024
|
+
# @param key [Object] the reference key
|
|
1025
|
+
# @return [Node] the predecessor node, or @nil_node if none exists
|
|
1026
|
+
def find_predecessor_node(key)
|
|
1027
|
+
# Check if key exists using O(1) hash lookup
|
|
1028
|
+
if (node = @hash_index[key])
|
|
1029
|
+
# Key exists: find predecessor in subtree or ancestors
|
|
1030
|
+
if node.left != @nil_node
|
|
1031
|
+
return rightmost(node.left)
|
|
1032
|
+
else
|
|
1033
|
+
# Walk up to find first ancestor where we came from the right
|
|
1034
|
+
current = node
|
|
1035
|
+
parent = current.parent
|
|
1036
|
+
while parent != @nil_node && current == parent.left
|
|
1037
|
+
current = parent
|
|
1038
|
+
parent = parent.parent
|
|
1039
|
+
end
|
|
1040
|
+
return parent
|
|
1041
|
+
end
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
# Key doesn't exist: descend tree tracking the best candidate
|
|
1045
|
+
current = @root
|
|
1046
|
+
predecessor = @nil_node
|
|
1047
|
+
while current != @nil_node
|
|
1048
|
+
cmp = key <=> current.key
|
|
1049
|
+
if cmp > 0
|
|
1050
|
+
predecessor = current # This node's key < given key
|
|
1051
|
+
current = current.right
|
|
1052
|
+
else
|
|
1053
|
+
current = current.left
|
|
1054
|
+
end
|
|
1055
|
+
end
|
|
1056
|
+
predecessor
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
# Finds the node with the smallest key that is larger than the given key.
|
|
1060
|
+
#
|
|
1061
|
+
# If the key exists in the tree, returns its successor node.
|
|
1062
|
+
# If the key does not exist, returns the smallest node with key > given key.
|
|
1063
|
+
#
|
|
1064
|
+
# @param key [Object] the reference key
|
|
1065
|
+
# @return [Node] the successor node, or @nil_node if none exists
|
|
1066
|
+
def find_successor_node(key)
|
|
1067
|
+
# Check if key exists using O(1) hash lookup
|
|
1068
|
+
if (node = @hash_index[key])
|
|
1069
|
+
# Key exists: find successor in subtree or ancestors
|
|
1070
|
+
if node.right != @nil_node
|
|
1071
|
+
return leftmost(node.right)
|
|
1072
|
+
else
|
|
1073
|
+
# Walk up to find first ancestor where we came from the left
|
|
1074
|
+
current = node
|
|
1075
|
+
parent = current.parent
|
|
1076
|
+
while parent != @nil_node && current == parent.right
|
|
1077
|
+
current = parent
|
|
1078
|
+
parent = parent.parent
|
|
1079
|
+
end
|
|
1080
|
+
return parent
|
|
1081
|
+
end
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
# Key doesn't exist: descend tree tracking the best candidate
|
|
1085
|
+
current = @root
|
|
1086
|
+
successor = @nil_node
|
|
1087
|
+
while current != @nil_node
|
|
1088
|
+
cmp = key <=> current.key
|
|
1089
|
+
if cmp < 0
|
|
1090
|
+
successor = current # This node's key > given key
|
|
1091
|
+
current = current.left
|
|
1092
|
+
else
|
|
1093
|
+
current = current.right
|
|
1094
|
+
end
|
|
1095
|
+
end
|
|
1096
|
+
successor
|
|
1097
|
+
end
|
|
1098
|
+
|
|
845
1099
|
# Finds the leftmost (minimum) node in a subtree.
|
|
846
1100
|
#
|
|
847
1101
|
# @param node [Node] the root of the subtree
|
|
@@ -994,7 +1248,39 @@ end
|
|
|
994
1248
|
# * Multiple values per key using arrays
|
|
995
1249
|
# * Separate methods for single deletion (`delete_one`) vs. all deletions (`delete`)
|
|
996
1250
|
# * Values for each key maintain insertion order
|
|
997
|
-
# *
|
|
1251
|
+
# * Configurable access to first or last value via `:last` option
|
|
1252
|
+
#
|
|
1253
|
+
# == Value Array Access
|
|
1254
|
+
#
|
|
1255
|
+
# For each key, values are stored in insertion order. Methods that access
|
|
1256
|
+
# a single value support a `:last` option to choose which end of the array:
|
|
1257
|
+
#
|
|
1258
|
+
# * +get(key)+, +get_first(key)+ - returns first value (oldest)
|
|
1259
|
+
# * +get(key, last: true)+, +get_last(key)+ - returns last value (newest)
|
|
1260
|
+
# * +delete_one(key)+, +delete_first(key)+ - removes first value
|
|
1261
|
+
# * +delete_one(key, last: true)+, +delete_last(key)+ - removes last value
|
|
1262
|
+
# * +prev(key)+, +succ(key)+ - returns first value of adjacent key
|
|
1263
|
+
# * +prev(key, last: true)+, +succ(key, last: true)+ - returns last value
|
|
1264
|
+
#
|
|
1265
|
+
# == Boundary Operations
|
|
1266
|
+
#
|
|
1267
|
+
# * +min+, +max+ - return [key, first_value] by default
|
|
1268
|
+
# * +min(last: true)+, +max(last: true)+ - return [key, last_value]
|
|
1269
|
+
# * +shift+ - removes and returns [smallest_key, first_value]
|
|
1270
|
+
# * +pop+ - removes and returns [largest_key, last_value]
|
|
1271
|
+
#
|
|
1272
|
+
# == Iteration Order
|
|
1273
|
+
#
|
|
1274
|
+
# When iterating over values, the order depends on the direction:
|
|
1275
|
+
#
|
|
1276
|
+
# * Forward iteration (+each+, +lt+, +gt+, etc.): Each key's values are
|
|
1277
|
+
# yielded in insertion order (first to last).
|
|
1278
|
+
#
|
|
1279
|
+
# * Reverse iteration (+reverse_each+, +lt(key, reverse: true)+, etc.):
|
|
1280
|
+
# Each key's values are yielded in reverse insertion order (last to first).
|
|
1281
|
+
#
|
|
1282
|
+
# This ensures consistent behavior where reverse iteration is truly the
|
|
1283
|
+
# mirror image of forward iteration.
|
|
998
1284
|
#
|
|
999
1285
|
# == Usage
|
|
1000
1286
|
#
|
|
@@ -1003,32 +1289,55 @@ end
|
|
|
1003
1289
|
# tree.insert(1, 'second one')
|
|
1004
1290
|
# tree.insert(2, 'two')
|
|
1005
1291
|
#
|
|
1006
|
-
# tree.size
|
|
1007
|
-
# tree.get(1)
|
|
1008
|
-
# tree.
|
|
1292
|
+
# tree.size # => 3 (total key-value pairs)
|
|
1293
|
+
# tree.get(1) # => "first one" (first value)
|
|
1294
|
+
# tree.get(1, last: true) # => "second one" (last value)
|
|
1295
|
+
# tree.get_all(1) # => ["first one", "second one"] (all values)
|
|
1009
1296
|
#
|
|
1010
|
-
# tree.delete_one(1)
|
|
1011
|
-
# tree.get(1)
|
|
1297
|
+
# tree.delete_one(1) # removes only "first one"
|
|
1298
|
+
# tree.get(1) # => "second one"
|
|
1012
1299
|
#
|
|
1013
|
-
# tree.delete(1)
|
|
1300
|
+
# tree.delete(1) # removes all remaining values for key 1
|
|
1014
1301
|
#
|
|
1015
1302
|
# @author Masahito Suzuki
|
|
1016
1303
|
# @since 0.1.2
|
|
1017
1304
|
class MultiRBTree < RBTree
|
|
1018
|
-
# Retrieves
|
|
1305
|
+
# Retrieves a value associated with the given key.
|
|
1019
1306
|
#
|
|
1020
1307
|
# @param key [Object] the key to look up
|
|
1021
|
-
# @
|
|
1308
|
+
# @param last [Boolean] if true, return the last value; otherwise return the first (default: false)
|
|
1309
|
+
# @return [Object, nil] the value for the key, or nil if not found
|
|
1022
1310
|
# @example
|
|
1023
1311
|
# tree = MultiRBTree.new
|
|
1024
1312
|
# tree.insert(1, 'first')
|
|
1025
1313
|
# tree.insert(1, 'second')
|
|
1026
|
-
# tree.get(1)
|
|
1027
|
-
|
|
1314
|
+
# tree.get(1) # => "first"
|
|
1315
|
+
# tree.get(1, last: true) # => "second"
|
|
1316
|
+
def get(key, last: false)
|
|
1028
1317
|
n = find_node(key)
|
|
1029
|
-
n == @nil_node || n.value.empty?
|
|
1318
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1319
|
+
last ? n.value.last : n.value.first
|
|
1030
1320
|
end
|
|
1031
|
-
|
|
1321
|
+
|
|
1322
|
+
# Retrieves the first value associated with the given key.
|
|
1323
|
+
#
|
|
1324
|
+
# @param key [Object] the key to look up
|
|
1325
|
+
# @return [Object, nil] the first value for the key, or nil if not found
|
|
1326
|
+
def get_first(key) = get(key, last: false)
|
|
1327
|
+
|
|
1328
|
+
# Retrieves the last value associated with the given key.
|
|
1329
|
+
#
|
|
1330
|
+
# @param key [Object] the key to look up
|
|
1331
|
+
# @return [Object, nil] the last value for the key, or nil if not found
|
|
1332
|
+
def get_last(key) = get(key, last: true)
|
|
1333
|
+
|
|
1334
|
+
# Returns the first value for the given key (for Hash-like access).
|
|
1335
|
+
#
|
|
1336
|
+
# Note: Unlike get(), this method does not accept options.
|
|
1337
|
+
#
|
|
1338
|
+
# @param key [Object] the key to look up
|
|
1339
|
+
# @return [Object, nil] the first value for the key, or nil if not found
|
|
1340
|
+
def [](key) = get(key)
|
|
1032
1341
|
|
|
1033
1342
|
# Retrieves all values associated with the given key.
|
|
1034
1343
|
#
|
|
@@ -1059,6 +1368,7 @@ class MultiRBTree < RBTree
|
|
|
1059
1368
|
def insert(key, value)
|
|
1060
1369
|
if (node = @hash_index[key])
|
|
1061
1370
|
node.value << value
|
|
1371
|
+
@size += 1
|
|
1062
1372
|
return true
|
|
1063
1373
|
end
|
|
1064
1374
|
y = @nil_node
|
|
@@ -1099,24 +1409,25 @@ class MultiRBTree < RBTree
|
|
|
1099
1409
|
true
|
|
1100
1410
|
end
|
|
1101
1411
|
|
|
1102
|
-
# Deletes
|
|
1412
|
+
# Deletes a single value for the specified key.
|
|
1103
1413
|
#
|
|
1104
|
-
# If the key has multiple values, removes only
|
|
1414
|
+
# If the key has multiple values, removes only one value.
|
|
1105
1415
|
# If this was the last value for the key, the node is removed from the tree.
|
|
1106
1416
|
#
|
|
1107
1417
|
# @param key [Object] the key to delete from
|
|
1418
|
+
# @param last [Boolean] if true, remove the last value; otherwise remove the first (default: false)
|
|
1108
1419
|
# @return [Object, nil] the deleted value, or nil if key not found
|
|
1109
1420
|
# @example
|
|
1110
1421
|
# tree = MultiRBTree.new
|
|
1111
1422
|
# tree.insert(1, 'first')
|
|
1112
1423
|
# tree.insert(1, 'second')
|
|
1113
|
-
# tree.delete_one(1)
|
|
1114
|
-
# tree.
|
|
1115
|
-
def delete_one(key)
|
|
1424
|
+
# tree.delete_one(1) # => "first"
|
|
1425
|
+
# tree.delete_one(1, last: true) # => "second" (if more values existed)
|
|
1426
|
+
def delete_one(key, last: false)
|
|
1116
1427
|
z = @hash_index[key] # O(1) lookup
|
|
1117
1428
|
return nil unless z
|
|
1118
1429
|
|
|
1119
|
-
value = z.value.shift
|
|
1430
|
+
value = last ? z.value.pop : z.value.shift
|
|
1120
1431
|
@size -= 1
|
|
1121
1432
|
if z.value.empty?
|
|
1122
1433
|
@hash_index.delete(key) # Remove from index when node removed
|
|
@@ -1125,6 +1436,18 @@ class MultiRBTree < RBTree
|
|
|
1125
1436
|
value
|
|
1126
1437
|
end
|
|
1127
1438
|
|
|
1439
|
+
# Deletes the first value for the specified key.
|
|
1440
|
+
#
|
|
1441
|
+
# @param key [Object] the key to delete from
|
|
1442
|
+
# @return [Object, nil] the deleted value, or nil if key not found
|
|
1443
|
+
def delete_first(key) = delete_one(key, last: false)
|
|
1444
|
+
|
|
1445
|
+
# Deletes the last value for the specified key.
|
|
1446
|
+
#
|
|
1447
|
+
# @param key [Object] the key to delete from
|
|
1448
|
+
# @return [Object, nil] the deleted value, or nil if key not found
|
|
1449
|
+
def delete_last(key) = delete_one(key, last: true)
|
|
1450
|
+
|
|
1128
1451
|
# Deletes all values for the specified key.
|
|
1129
1452
|
#
|
|
1130
1453
|
# Removes the node and all associated values.
|
|
@@ -1172,18 +1495,32 @@ class MultiRBTree < RBTree
|
|
|
1172
1495
|
[n.key, val]
|
|
1173
1496
|
end
|
|
1174
1497
|
|
|
1175
|
-
def min
|
|
1498
|
+
def min(last: false)
|
|
1176
1499
|
return nil if @min_node == @nil_node || @min_node.value.empty?
|
|
1177
|
-
[@min_node.key, @min_node.value.first]
|
|
1500
|
+
[@min_node.key, last ? @min_node.value.last : @min_node.value.first]
|
|
1178
1501
|
end
|
|
1179
1502
|
|
|
1180
|
-
def max
|
|
1503
|
+
def max(last: false)
|
|
1181
1504
|
n = rightmost(@root)
|
|
1182
|
-
n == @nil_node || n.value.empty?
|
|
1505
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1506
|
+
[n.key, last ? n.value.last : n.value.first]
|
|
1183
1507
|
end
|
|
1184
1508
|
|
|
1185
1509
|
def nearest(key)
|
|
1186
|
-
|
|
1510
|
+
n = find_nearest_node(key)
|
|
1511
|
+
n == @nil_node || n.value.empty? ? nil : [n.key, n.value.first]
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
def prev(key, last: false)
|
|
1515
|
+
n = find_predecessor_node(key)
|
|
1516
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1517
|
+
[n.key, last ? n.value.last : n.value.first]
|
|
1518
|
+
end
|
|
1519
|
+
|
|
1520
|
+
def succ(key, last: false)
|
|
1521
|
+
n = find_successor_node(key)
|
|
1522
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1523
|
+
[n.key, last ? n.value.last : n.value.first]
|
|
1187
1524
|
end
|
|
1188
1525
|
|
|
1189
1526
|
private
|
|
@@ -1273,6 +1610,65 @@ class MultiRBTree < RBTree
|
|
|
1273
1610
|
traverse_between(node.right, min, max, include_min, include_max, &block)
|
|
1274
1611
|
end
|
|
1275
1612
|
end
|
|
1613
|
+
|
|
1614
|
+
def traverse_lt_desc(node, key, &block)
|
|
1615
|
+
return if node == @nil_node
|
|
1616
|
+
|
|
1617
|
+
if (node.key <=> key) < 0
|
|
1618
|
+
traverse_lt_desc(node.right, key, &block)
|
|
1619
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1620
|
+
end
|
|
1621
|
+
traverse_lt_desc(node.left, key, &block)
|
|
1622
|
+
end
|
|
1623
|
+
|
|
1624
|
+
def traverse_lte_desc(node, key, &block)
|
|
1625
|
+
return if node == @nil_node
|
|
1626
|
+
|
|
1627
|
+
if (node.key <=> key) <= 0
|
|
1628
|
+
traverse_lte_desc(node.right, key, &block)
|
|
1629
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1630
|
+
end
|
|
1631
|
+
traverse_lte_desc(node.left, key, &block)
|
|
1632
|
+
end
|
|
1633
|
+
|
|
1634
|
+
def traverse_gt_desc(node, key, &block)
|
|
1635
|
+
return if node == @nil_node
|
|
1636
|
+
|
|
1637
|
+
traverse_gt_desc(node.right, key, &block)
|
|
1638
|
+
if (node.key <=> key) > 0
|
|
1639
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1640
|
+
traverse_gt_desc(node.left, key, &block)
|
|
1641
|
+
end
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
def traverse_gte_desc(node, key, &block)
|
|
1645
|
+
return if node == @nil_node
|
|
1646
|
+
|
|
1647
|
+
traverse_gte_desc(node.right, key, &block)
|
|
1648
|
+
if (node.key <=> key) >= 0
|
|
1649
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1650
|
+
traverse_gte_desc(node.left, key, &block)
|
|
1651
|
+
end
|
|
1652
|
+
end
|
|
1653
|
+
|
|
1654
|
+
def traverse_between_desc(node, min, max, include_min, include_max, &block)
|
|
1655
|
+
return if node == @nil_node
|
|
1656
|
+
|
|
1657
|
+
if (node.key <=> max) < 0
|
|
1658
|
+
traverse_between_desc(node.right, min, max, include_min, include_max, &block)
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1661
|
+
greater = include_min ? (node.key <=> min) >= 0 : (node.key <=> min) > 0
|
|
1662
|
+
less = include_max ? (node.key <=> max) <= 0 : (node.key <=> max) < 0
|
|
1663
|
+
|
|
1664
|
+
if greater && less
|
|
1665
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1668
|
+
if (node.key <=> min) > 0
|
|
1669
|
+
traverse_between_desc(node.left, min, max, include_min, include_max, &block)
|
|
1670
|
+
end
|
|
1671
|
+
end
|
|
1276
1672
|
end
|
|
1277
1673
|
|
|
1278
1674
|
# Internal node structure for RBTree.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rbtree-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Masahito Suzuki
|
|
@@ -47,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
48
|
version: '0'
|
|
49
49
|
requirements: []
|
|
50
|
-
rubygems_version:
|
|
50
|
+
rubygems_version: 4.0.3
|
|
51
51
|
specification_version: 4
|
|
52
52
|
summary: A pure Ruby implementation of Red-Black Tree with multi-value support
|
|
53
53
|
test_files: []
|