rbtree-ruby 0.1.6 → 0.1.8
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 -4
- data/README.md +71 -10
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +423 -72
- 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: ad4ff4e15079d2034a111e0180f1abbf5f433283de5c125f45569f9b69f558fc
|
|
4
|
+
data.tar.gz: 7355e1ae3964e79aa8d110c52e38672641c8d67ac1cb4cfbbd974fe6298f07f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e62ddc40bfc16d4afaf9430b679b03cfb50e89d3a02d7ab43a190b168015d4c6605baf579cc55abc0d42db207465538f99e81618beca873a1b4c654d1ab0c72f
|
|
7
|
+
data.tar.gz: d11f0258d1ea5ea2686cc8bb581eb3b5c524554bf2182d68d54759e592d58d57e463bfa87b13dbabb67e68becfa8648108b267751791a1877e49bc7c6e2f6a06
|
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.1.8] - 2026-01-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Range Query Returns Enumerator**: `lt`, `lte`, `gt`, `gte`, `between` now return an `Enumerator` instead of an `Array` when no block is given
|
|
12
|
+
- Enables lazy evaluation: `tree.gt(100).lazy.take(5).to_a`
|
|
13
|
+
- Enables chaining: `tree.lt(50).map { |k, v| k }`
|
|
14
|
+
- Use `.to_a` to get an Array: `tree.lt(50).to_a`
|
|
15
|
+
- **Breaking**: Code using direct array access like `tree.lt(50)[0]` must change to `tree.lt(50).first`
|
|
16
|
+
|
|
17
|
+
## [0.1.7] - 2026-01-14
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Predecessor/Successor Search**: New `prev(key)` and `succ(key)` methods
|
|
21
|
+
- Returns the key-value pair immediately before/after the given key
|
|
22
|
+
- Works even if the key doesn't exist in the tree (returns nearest neighbor)
|
|
23
|
+
- Uses hash index for O(1) existence check, then O(log n) tree traversal
|
|
24
|
+
- **Reverse Range Queries**: All range query methods now support `:reverse` option
|
|
25
|
+
- `lt(key, reverse: true)`, `lte`, `gt`, `gte`, `between`
|
|
26
|
+
- Returns results in descending order instead of ascending
|
|
27
|
+
- **MultiRBTree Enhancements**:
|
|
28
|
+
- `get(key, last: false)` - Choose first or last value from array
|
|
29
|
+
- `get_first(key)`, `get_last(key)` - Convenient aliases
|
|
30
|
+
- `delete_one(key, last: false)` - Choose which end to delete from
|
|
31
|
+
- `delete_first(key)`, `delete_last(key)` - Convenient aliases
|
|
32
|
+
- `min(last: false)`, `max(last: false)` - Choose first or last value
|
|
33
|
+
- `prev(key, last: false)`, `succ(key, last: false)` - Choose first or last value
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **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
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- **MultiRBTree size tracking**: Fixed bug where `insert` did not increment size when key already existed in hash index
|
|
40
|
+
|
|
8
41
|
## [0.1.6] - 2026-01-13
|
|
9
42
|
|
|
10
43
|
### Changed
|
|
@@ -83,8 +116,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
83
116
|
- ASCII diagrams for tree rotation operations
|
|
84
117
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
85
118
|
|
|
119
|
+
[0.1.8]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.8
|
|
120
|
+
[0.1.7]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.7
|
|
86
121
|
[0.1.6]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.6
|
|
87
|
-
[0.1.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.5
|
|
88
|
-
[0.1.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.4
|
|
89
|
-
[0.1.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.3
|
|
90
|
-
[0.1.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.2
|
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,78 +329,82 @@ 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
|
-
# @return [
|
|
334
|
+
# @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
|
|
334
335
|
# @example
|
|
335
336
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
336
|
-
# tree.lt(3) # => [[1, "one"], [2, "two"]]
|
|
337
|
-
# tree.lt(3
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
337
|
+
# tree.lt(3).to_a # => [[1, "one"], [2, "two"]]
|
|
338
|
+
# tree.lt(3, reverse: true).first # => [2, "two"]
|
|
339
|
+
# tree.lt(3) { |k, v| puts k } # prints keys, returns self
|
|
340
|
+
def lt(key, reverse: false, &block)
|
|
341
|
+
return enum_for(:lt, key, reverse: reverse) unless block_given?
|
|
342
|
+
if reverse
|
|
343
|
+
traverse_lt_desc(@root, key, &block)
|
|
342
344
|
else
|
|
343
|
-
|
|
344
|
-
traverse_lt(@root, key) { |k, v| res << [k, v] }
|
|
345
|
-
res
|
|
345
|
+
traverse_lt(@root, key, &block)
|
|
346
346
|
end
|
|
347
|
+
self
|
|
347
348
|
end
|
|
348
349
|
|
|
349
350
|
# Retrieves all key-value pairs with keys less than or equal to the specified key.
|
|
350
351
|
#
|
|
351
352
|
# @param key [Object] the upper bound (inclusive)
|
|
353
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
352
354
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
353
|
-
# @return [
|
|
355
|
+
# @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
|
|
354
356
|
# @example
|
|
355
357
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
356
|
-
# tree.lte(3) # => [[1, "one"], [2, "two"], [3, "three"]]
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
358
|
+
# tree.lte(3).to_a # => [[1, "one"], [2, "two"], [3, "three"]]
|
|
359
|
+
# tree.lte(3, reverse: true).first # => [3, "three"]
|
|
360
|
+
def lte(key, reverse: false, &block)
|
|
361
|
+
return enum_for(:lte, key, reverse: reverse) unless block_given?
|
|
362
|
+
if reverse
|
|
363
|
+
traverse_lte_desc(@root, key, &block)
|
|
361
364
|
else
|
|
362
|
-
|
|
363
|
-
traverse_lte(@root, key) { |k, v| res << [k, v] }
|
|
364
|
-
res
|
|
365
|
+
traverse_lte(@root, key, &block)
|
|
365
366
|
end
|
|
367
|
+
self
|
|
366
368
|
end
|
|
367
369
|
|
|
368
370
|
# Retrieves all key-value pairs with keys greater than the specified key.
|
|
369
371
|
#
|
|
370
372
|
# @param key [Object] the lower bound (exclusive)
|
|
373
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
371
374
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
372
|
-
# @return [
|
|
375
|
+
# @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
|
|
373
376
|
# @example
|
|
374
377
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
375
|
-
# tree.gt(2) # => [[3, "three"], [4, "four"]]
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
378
|
+
# tree.gt(2).to_a # => [[3, "three"], [4, "four"]]
|
|
379
|
+
# tree.gt(2, reverse: true).first # => [4, "four"]
|
|
380
|
+
def gt(key, reverse: false, &block)
|
|
381
|
+
return enum_for(:gt, key, reverse: reverse) unless block_given?
|
|
382
|
+
if reverse
|
|
383
|
+
traverse_gt_desc(@root, key, &block)
|
|
380
384
|
else
|
|
381
|
-
|
|
382
|
-
traverse_gt(@root, key) { |k, v| res << [k, v] }
|
|
383
|
-
res
|
|
385
|
+
traverse_gt(@root, key, &block)
|
|
384
386
|
end
|
|
387
|
+
self
|
|
385
388
|
end
|
|
386
389
|
|
|
387
390
|
# Retrieves all key-value pairs with keys greater than or equal to the specified key.
|
|
388
391
|
#
|
|
389
392
|
# @param key [Object] the lower bound (inclusive)
|
|
393
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
390
394
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
391
|
-
# @return [
|
|
395
|
+
# @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
|
|
392
396
|
# @example
|
|
393
397
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
|
|
394
|
-
# tree.gte(2) # => [[2, "two"], [3, "three"], [4, "four"]]
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
398
|
+
# tree.gte(2).to_a # => [[2, "two"], [3, "three"], [4, "four"]]
|
|
399
|
+
# tree.gte(2, reverse: true).first # => [4, "four"]
|
|
400
|
+
def gte(key, reverse: false, &block)
|
|
401
|
+
return enum_for(:gte, key, reverse: reverse) unless block_given?
|
|
402
|
+
if reverse
|
|
403
|
+
traverse_gte_desc(@root, key, &block)
|
|
399
404
|
else
|
|
400
|
-
|
|
401
|
-
traverse_gte(@root, key) { |k, v| res << [k, v] }
|
|
402
|
-
res
|
|
405
|
+
traverse_gte(@root, key, &block)
|
|
403
406
|
end
|
|
407
|
+
self
|
|
404
408
|
end
|
|
405
409
|
|
|
406
410
|
# Retrieves all key-value pairs with keys within the specified range.
|
|
@@ -409,22 +413,21 @@ class RBTree
|
|
|
409
413
|
# @param max [Object] the upper bound
|
|
410
414
|
# @param include_min [Boolean] whether to include the lower bound (default: true)
|
|
411
415
|
# @param include_max [Boolean] whether to include the upper bound (default: true)
|
|
416
|
+
# @param reverse [Boolean] if true, iterate in descending order (default: false)
|
|
412
417
|
# @yield [key, value] each matching key-value pair (if block given)
|
|
413
|
-
# @return [
|
|
418
|
+
# @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
|
|
414
419
|
# @example
|
|
415
420
|
# tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five'})
|
|
416
|
-
# tree.between(2, 4) # => [[2, "two"], [3, "three"], [4, "four"]]
|
|
417
|
-
# tree.between(2, 4,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if
|
|
421
|
-
|
|
422
|
-
self
|
|
421
|
+
# tree.between(2, 4).to_a # => [[2, "two"], [3, "three"], [4, "four"]]
|
|
422
|
+
# tree.between(2, 4, reverse: true).first # => [4, "four"]
|
|
423
|
+
def between(min, max, include_min: true, include_max: true, reverse: false, &block)
|
|
424
|
+
return enum_for(:between, min, max, include_min: include_min, include_max: include_max, reverse: reverse) unless block_given?
|
|
425
|
+
if reverse
|
|
426
|
+
traverse_between_desc(@root, min, max, include_min, include_max, &block)
|
|
423
427
|
else
|
|
424
|
-
|
|
425
|
-
traverse_between(@root, min, max, include_min, include_max) { |k, v| res << [k, v] }
|
|
426
|
-
res
|
|
428
|
+
traverse_between(@root, min, max, include_min, include_max, &block)
|
|
427
429
|
end
|
|
430
|
+
self
|
|
428
431
|
end
|
|
429
432
|
|
|
430
433
|
# Returns the key-value pair with the key closest to the given key.
|
|
@@ -445,6 +448,40 @@ class RBTree
|
|
|
445
448
|
n == @nil_node ? nil : [n.key, n.value]
|
|
446
449
|
end
|
|
447
450
|
|
|
451
|
+
# Returns the key-value pair with the largest key that is smaller than the given key.
|
|
452
|
+
#
|
|
453
|
+
# If the key exists in the tree, returns the predecessor (previous element).
|
|
454
|
+
# If the key does not exist, returns the largest key-value pair with key < given key.
|
|
455
|
+
#
|
|
456
|
+
# @param key [Object] the reference key
|
|
457
|
+
# @return [Array(Object, Object), nil] a two-element array [key, value], or nil if no predecessor exists
|
|
458
|
+
# @example
|
|
459
|
+
# tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
|
|
460
|
+
# tree.prev(5) # => [3, "three"]
|
|
461
|
+
# tree.prev(4) # => [3, "three"] (4 does not exist)
|
|
462
|
+
# tree.prev(1) # => nil (no predecessor)
|
|
463
|
+
def prev(key)
|
|
464
|
+
n = find_predecessor_node(key)
|
|
465
|
+
n == @nil_node ? nil : [n.key, n.value]
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Returns the key-value pair with the smallest key that is larger than the given key.
|
|
469
|
+
#
|
|
470
|
+
# If the key exists in the tree, returns the successor (next element).
|
|
471
|
+
# If the key does not exist, returns the smallest key-value pair with key > given key.
|
|
472
|
+
#
|
|
473
|
+
# @param key [Object] the reference key
|
|
474
|
+
# @return [Array(Object, Object), nil] a two-element array [key, value], or nil if no successor exists
|
|
475
|
+
# @example
|
|
476
|
+
# tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
|
|
477
|
+
# tree.succ(5) # => [7, "seven"]
|
|
478
|
+
# tree.succ(4) # => [5, "five"] (4 does not exist)
|
|
479
|
+
# tree.succ(7) # => nil (no successor)
|
|
480
|
+
def succ(key)
|
|
481
|
+
n = find_successor_node(key)
|
|
482
|
+
n == @nil_node ? nil : [n.key, n.value]
|
|
483
|
+
end
|
|
484
|
+
|
|
448
485
|
# Validates the red-black tree properties.
|
|
449
486
|
#
|
|
450
487
|
# Checks that:
|
|
@@ -593,6 +630,98 @@ class RBTree
|
|
|
593
630
|
end
|
|
594
631
|
end
|
|
595
632
|
|
|
633
|
+
# Traverses nodes with keys less than the specified key in descending order.
|
|
634
|
+
#
|
|
635
|
+
# @param node [Node] the current node
|
|
636
|
+
# @param key [Object] the upper bound (exclusive)
|
|
637
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
638
|
+
# @return [void]
|
|
639
|
+
def traverse_lt_desc(node, key, &block)
|
|
640
|
+
return if node == @nil_node
|
|
641
|
+
|
|
642
|
+
if (node.key <=> key) < 0
|
|
643
|
+
traverse_lt_desc(node.right, key, &block)
|
|
644
|
+
yield node.key, node.value
|
|
645
|
+
end
|
|
646
|
+
traverse_lt_desc(node.left, key, &block)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# Traverses nodes with keys less than or equal to the specified key in descending order.
|
|
650
|
+
#
|
|
651
|
+
# @param node [Node] the current node
|
|
652
|
+
# @param key [Object] the upper bound (inclusive)
|
|
653
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
654
|
+
# @return [void]
|
|
655
|
+
def traverse_lte_desc(node, key, &block)
|
|
656
|
+
return if node == @nil_node
|
|
657
|
+
|
|
658
|
+
if (node.key <=> key) <= 0
|
|
659
|
+
traverse_lte_desc(node.right, key, &block)
|
|
660
|
+
yield node.key, node.value
|
|
661
|
+
end
|
|
662
|
+
traverse_lte_desc(node.left, key, &block)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Traverses nodes with keys greater than the specified key in descending order.
|
|
666
|
+
#
|
|
667
|
+
# @param node [Node] the current node
|
|
668
|
+
# @param key [Object] the lower bound (exclusive)
|
|
669
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
670
|
+
# @return [void]
|
|
671
|
+
def traverse_gt_desc(node, key, &block)
|
|
672
|
+
return if node == @nil_node
|
|
673
|
+
|
|
674
|
+
traverse_gt_desc(node.right, key, &block)
|
|
675
|
+
if (node.key <=> key) > 0
|
|
676
|
+
yield node.key, node.value
|
|
677
|
+
traverse_gt_desc(node.left, key, &block)
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Traverses nodes with keys greater than or equal to the specified key in descending order.
|
|
682
|
+
#
|
|
683
|
+
# @param node [Node] the current node
|
|
684
|
+
# @param key [Object] the lower bound (inclusive)
|
|
685
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
686
|
+
# @return [void]
|
|
687
|
+
def traverse_gte_desc(node, key, &block)
|
|
688
|
+
return if node == @nil_node
|
|
689
|
+
|
|
690
|
+
traverse_gte_desc(node.right, key, &block)
|
|
691
|
+
if (node.key <=> key) >= 0
|
|
692
|
+
yield node.key, node.value
|
|
693
|
+
traverse_gte_desc(node.left, key, &block)
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Traverses nodes with keys within the specified range in descending order.
|
|
698
|
+
#
|
|
699
|
+
# @param node [Node] the current node
|
|
700
|
+
# @param min [Object] the lower bound
|
|
701
|
+
# @param max [Object] the upper bound
|
|
702
|
+
# @param include_min [Boolean] whether to include the lower bound
|
|
703
|
+
# @param include_max [Boolean] whether to include the upper bound
|
|
704
|
+
# @yield [key, value] each matching key-value pair in descending order
|
|
705
|
+
# @return [void]
|
|
706
|
+
def traverse_between_desc(node, min, max, include_min, include_max, &block)
|
|
707
|
+
return if node == @nil_node
|
|
708
|
+
|
|
709
|
+
if (node.key <=> max) < 0
|
|
710
|
+
traverse_between_desc(node.right, min, max, include_min, include_max, &block)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
greater = include_min ? (node.key <=> min) >= 0 : (node.key <=> min) > 0
|
|
714
|
+
less = include_max ? (node.key <=> max) <= 0 : (node.key <=> max) < 0
|
|
715
|
+
|
|
716
|
+
if greater && less
|
|
717
|
+
yield node.key, node.value
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
if (node.key <=> min) > 0
|
|
721
|
+
traverse_between_desc(node.left, min, max, include_min, include_max, &block)
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
|
|
596
725
|
# Restores red-black tree properties after insertion.
|
|
597
726
|
#
|
|
598
727
|
# This method fixes any violations of red-black properties that may occur
|
|
@@ -842,6 +971,86 @@ class RBTree
|
|
|
842
971
|
closest
|
|
843
972
|
end
|
|
844
973
|
|
|
974
|
+
# Finds the node with the largest key that is smaller than the given key.
|
|
975
|
+
#
|
|
976
|
+
# If the key exists in the tree, returns its predecessor node.
|
|
977
|
+
# If the key does not exist, returns the largest node with key < given key.
|
|
978
|
+
#
|
|
979
|
+
# @param key [Object] the reference key
|
|
980
|
+
# @return [Node] the predecessor node, or @nil_node if none exists
|
|
981
|
+
def find_predecessor_node(key)
|
|
982
|
+
# Check if key exists using O(1) hash lookup
|
|
983
|
+
if (node = @hash_index[key])
|
|
984
|
+
# Key exists: find predecessor in subtree or ancestors
|
|
985
|
+
if node.left != @nil_node
|
|
986
|
+
return rightmost(node.left)
|
|
987
|
+
else
|
|
988
|
+
# Walk up to find first ancestor where we came from the right
|
|
989
|
+
current = node
|
|
990
|
+
parent = current.parent
|
|
991
|
+
while parent != @nil_node && current == parent.left
|
|
992
|
+
current = parent
|
|
993
|
+
parent = parent.parent
|
|
994
|
+
end
|
|
995
|
+
return parent
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
# Key doesn't exist: descend tree tracking the best candidate
|
|
1000
|
+
current = @root
|
|
1001
|
+
predecessor = @nil_node
|
|
1002
|
+
while current != @nil_node
|
|
1003
|
+
cmp = key <=> current.key
|
|
1004
|
+
if cmp > 0
|
|
1005
|
+
predecessor = current # This node's key < given key
|
|
1006
|
+
current = current.right
|
|
1007
|
+
else
|
|
1008
|
+
current = current.left
|
|
1009
|
+
end
|
|
1010
|
+
end
|
|
1011
|
+
predecessor
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
# Finds the node with the smallest key that is larger than the given key.
|
|
1015
|
+
#
|
|
1016
|
+
# If the key exists in the tree, returns its successor node.
|
|
1017
|
+
# If the key does not exist, returns the smallest node with key > given key.
|
|
1018
|
+
#
|
|
1019
|
+
# @param key [Object] the reference key
|
|
1020
|
+
# @return [Node] the successor node, or @nil_node if none exists
|
|
1021
|
+
def find_successor_node(key)
|
|
1022
|
+
# Check if key exists using O(1) hash lookup
|
|
1023
|
+
if (node = @hash_index[key])
|
|
1024
|
+
# Key exists: find successor in subtree or ancestors
|
|
1025
|
+
if node.right != @nil_node
|
|
1026
|
+
return leftmost(node.right)
|
|
1027
|
+
else
|
|
1028
|
+
# Walk up to find first ancestor where we came from the left
|
|
1029
|
+
current = node
|
|
1030
|
+
parent = current.parent
|
|
1031
|
+
while parent != @nil_node && current == parent.right
|
|
1032
|
+
current = parent
|
|
1033
|
+
parent = parent.parent
|
|
1034
|
+
end
|
|
1035
|
+
return parent
|
|
1036
|
+
end
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
# Key doesn't exist: descend tree tracking the best candidate
|
|
1040
|
+
current = @root
|
|
1041
|
+
successor = @nil_node
|
|
1042
|
+
while current != @nil_node
|
|
1043
|
+
cmp = key <=> current.key
|
|
1044
|
+
if cmp < 0
|
|
1045
|
+
successor = current # This node's key > given key
|
|
1046
|
+
current = current.left
|
|
1047
|
+
else
|
|
1048
|
+
current = current.right
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
successor
|
|
1052
|
+
end
|
|
1053
|
+
|
|
845
1054
|
# Finds the leftmost (minimum) node in a subtree.
|
|
846
1055
|
#
|
|
847
1056
|
# @param node [Node] the root of the subtree
|
|
@@ -994,7 +1203,39 @@ end
|
|
|
994
1203
|
# * Multiple values per key using arrays
|
|
995
1204
|
# * Separate methods for single deletion (`delete_one`) vs. all deletions (`delete`)
|
|
996
1205
|
# * Values for each key maintain insertion order
|
|
997
|
-
# *
|
|
1206
|
+
# * Configurable access to first or last value via `:last` option
|
|
1207
|
+
#
|
|
1208
|
+
# == Value Array Access
|
|
1209
|
+
#
|
|
1210
|
+
# For each key, values are stored in insertion order. Methods that access
|
|
1211
|
+
# a single value support a `:last` option to choose which end of the array:
|
|
1212
|
+
#
|
|
1213
|
+
# * +get(key)+, +get_first(key)+ - returns first value (oldest)
|
|
1214
|
+
# * +get(key, last: true)+, +get_last(key)+ - returns last value (newest)
|
|
1215
|
+
# * +delete_one(key)+, +delete_first(key)+ - removes first value
|
|
1216
|
+
# * +delete_one(key, last: true)+, +delete_last(key)+ - removes last value
|
|
1217
|
+
# * +prev(key)+, +succ(key)+ - returns first value of adjacent key
|
|
1218
|
+
# * +prev(key, last: true)+, +succ(key, last: true)+ - returns last value
|
|
1219
|
+
#
|
|
1220
|
+
# == Boundary Operations
|
|
1221
|
+
#
|
|
1222
|
+
# * +min+, +max+ - return [key, first_value] by default
|
|
1223
|
+
# * +min(last: true)+, +max(last: true)+ - return [key, last_value]
|
|
1224
|
+
# * +shift+ - removes and returns [smallest_key, first_value]
|
|
1225
|
+
# * +pop+ - removes and returns [largest_key, last_value]
|
|
1226
|
+
#
|
|
1227
|
+
# == Iteration Order
|
|
1228
|
+
#
|
|
1229
|
+
# When iterating over values, the order depends on the direction:
|
|
1230
|
+
#
|
|
1231
|
+
# * Forward iteration (+each+, +lt+, +gt+, etc.): Each key's values are
|
|
1232
|
+
# yielded in insertion order (first to last).
|
|
1233
|
+
#
|
|
1234
|
+
# * Reverse iteration (+reverse_each+, +lt(key, reverse: true)+, etc.):
|
|
1235
|
+
# Each key's values are yielded in reverse insertion order (last to first).
|
|
1236
|
+
#
|
|
1237
|
+
# This ensures consistent behavior where reverse iteration is truly the
|
|
1238
|
+
# mirror image of forward iteration.
|
|
998
1239
|
#
|
|
999
1240
|
# == Usage
|
|
1000
1241
|
#
|
|
@@ -1003,32 +1244,55 @@ end
|
|
|
1003
1244
|
# tree.insert(1, 'second one')
|
|
1004
1245
|
# tree.insert(2, 'two')
|
|
1005
1246
|
#
|
|
1006
|
-
# tree.size
|
|
1007
|
-
# tree.get(1)
|
|
1008
|
-
# tree.
|
|
1247
|
+
# tree.size # => 3 (total key-value pairs)
|
|
1248
|
+
# tree.get(1) # => "first one" (first value)
|
|
1249
|
+
# tree.get(1, last: true) # => "second one" (last value)
|
|
1250
|
+
# tree.get_all(1) # => ["first one", "second one"] (all values)
|
|
1009
1251
|
#
|
|
1010
|
-
# tree.delete_one(1)
|
|
1011
|
-
# tree.get(1)
|
|
1252
|
+
# tree.delete_one(1) # removes only "first one"
|
|
1253
|
+
# tree.get(1) # => "second one"
|
|
1012
1254
|
#
|
|
1013
|
-
# tree.delete(1)
|
|
1255
|
+
# tree.delete(1) # removes all remaining values for key 1
|
|
1014
1256
|
#
|
|
1015
1257
|
# @author Masahito Suzuki
|
|
1016
1258
|
# @since 0.1.2
|
|
1017
1259
|
class MultiRBTree < RBTree
|
|
1018
|
-
# Retrieves
|
|
1260
|
+
# Retrieves a value associated with the given key.
|
|
1019
1261
|
#
|
|
1020
1262
|
# @param key [Object] the key to look up
|
|
1021
|
-
# @
|
|
1263
|
+
# @param last [Boolean] if true, return the last value; otherwise return the first (default: false)
|
|
1264
|
+
# @return [Object, nil] the value for the key, or nil if not found
|
|
1022
1265
|
# @example
|
|
1023
1266
|
# tree = MultiRBTree.new
|
|
1024
1267
|
# tree.insert(1, 'first')
|
|
1025
1268
|
# tree.insert(1, 'second')
|
|
1026
|
-
# tree.get(1)
|
|
1027
|
-
|
|
1269
|
+
# tree.get(1) # => "first"
|
|
1270
|
+
# tree.get(1, last: true) # => "second"
|
|
1271
|
+
def get(key, last: false)
|
|
1028
1272
|
n = find_node(key)
|
|
1029
|
-
n == @nil_node || n.value.empty?
|
|
1273
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1274
|
+
last ? n.value.last : n.value.first
|
|
1030
1275
|
end
|
|
1031
|
-
|
|
1276
|
+
|
|
1277
|
+
# Retrieves the first value associated with the given key.
|
|
1278
|
+
#
|
|
1279
|
+
# @param key [Object] the key to look up
|
|
1280
|
+
# @return [Object, nil] the first value for the key, or nil if not found
|
|
1281
|
+
def get_first(key) = get(key, last: false)
|
|
1282
|
+
|
|
1283
|
+
# Retrieves the last value associated with the given key.
|
|
1284
|
+
#
|
|
1285
|
+
# @param key [Object] the key to look up
|
|
1286
|
+
# @return [Object, nil] the last value for the key, or nil if not found
|
|
1287
|
+
def get_last(key) = get(key, last: true)
|
|
1288
|
+
|
|
1289
|
+
# Returns the first value for the given key (for Hash-like access).
|
|
1290
|
+
#
|
|
1291
|
+
# Note: Unlike get(), this method does not accept options.
|
|
1292
|
+
#
|
|
1293
|
+
# @param key [Object] the key to look up
|
|
1294
|
+
# @return [Object, nil] the first value for the key, or nil if not found
|
|
1295
|
+
def [](key) = get(key)
|
|
1032
1296
|
|
|
1033
1297
|
# Retrieves all values associated with the given key.
|
|
1034
1298
|
#
|
|
@@ -1059,6 +1323,7 @@ class MultiRBTree < RBTree
|
|
|
1059
1323
|
def insert(key, value)
|
|
1060
1324
|
if (node = @hash_index[key])
|
|
1061
1325
|
node.value << value
|
|
1326
|
+
@size += 1
|
|
1062
1327
|
return true
|
|
1063
1328
|
end
|
|
1064
1329
|
y = @nil_node
|
|
@@ -1099,24 +1364,25 @@ class MultiRBTree < RBTree
|
|
|
1099
1364
|
true
|
|
1100
1365
|
end
|
|
1101
1366
|
|
|
1102
|
-
# Deletes
|
|
1367
|
+
# Deletes a single value for the specified key.
|
|
1103
1368
|
#
|
|
1104
|
-
# If the key has multiple values, removes only
|
|
1369
|
+
# If the key has multiple values, removes only one value.
|
|
1105
1370
|
# If this was the last value for the key, the node is removed from the tree.
|
|
1106
1371
|
#
|
|
1107
1372
|
# @param key [Object] the key to delete from
|
|
1373
|
+
# @param last [Boolean] if true, remove the last value; otherwise remove the first (default: false)
|
|
1108
1374
|
# @return [Object, nil] the deleted value, or nil if key not found
|
|
1109
1375
|
# @example
|
|
1110
1376
|
# tree = MultiRBTree.new
|
|
1111
1377
|
# tree.insert(1, 'first')
|
|
1112
1378
|
# tree.insert(1, 'second')
|
|
1113
|
-
# tree.delete_one(1)
|
|
1114
|
-
# tree.
|
|
1115
|
-
def delete_one(key)
|
|
1379
|
+
# tree.delete_one(1) # => "first"
|
|
1380
|
+
# tree.delete_one(1, last: true) # => "second" (if more values existed)
|
|
1381
|
+
def delete_one(key, last: false)
|
|
1116
1382
|
z = @hash_index[key] # O(1) lookup
|
|
1117
1383
|
return nil unless z
|
|
1118
1384
|
|
|
1119
|
-
value = z.value.shift
|
|
1385
|
+
value = last ? z.value.pop : z.value.shift
|
|
1120
1386
|
@size -= 1
|
|
1121
1387
|
if z.value.empty?
|
|
1122
1388
|
@hash_index.delete(key) # Remove from index when node removed
|
|
@@ -1125,6 +1391,18 @@ class MultiRBTree < RBTree
|
|
|
1125
1391
|
value
|
|
1126
1392
|
end
|
|
1127
1393
|
|
|
1394
|
+
# Deletes the first value for the specified key.
|
|
1395
|
+
#
|
|
1396
|
+
# @param key [Object] the key to delete from
|
|
1397
|
+
# @return [Object, nil] the deleted value, or nil if key not found
|
|
1398
|
+
def delete_first(key) = delete_one(key, last: false)
|
|
1399
|
+
|
|
1400
|
+
# Deletes the last value for the specified key.
|
|
1401
|
+
#
|
|
1402
|
+
# @param key [Object] the key to delete from
|
|
1403
|
+
# @return [Object, nil] the deleted value, or nil if key not found
|
|
1404
|
+
def delete_last(key) = delete_one(key, last: true)
|
|
1405
|
+
|
|
1128
1406
|
# Deletes all values for the specified key.
|
|
1129
1407
|
#
|
|
1130
1408
|
# Removes the node and all associated values.
|
|
@@ -1172,18 +1450,32 @@ class MultiRBTree < RBTree
|
|
|
1172
1450
|
[n.key, val]
|
|
1173
1451
|
end
|
|
1174
1452
|
|
|
1175
|
-
def min
|
|
1453
|
+
def min(last: false)
|
|
1176
1454
|
return nil if @min_node == @nil_node || @min_node.value.empty?
|
|
1177
|
-
[@min_node.key, @min_node.value.first]
|
|
1455
|
+
[@min_node.key, last ? @min_node.value.last : @min_node.value.first]
|
|
1178
1456
|
end
|
|
1179
1457
|
|
|
1180
|
-
def max
|
|
1458
|
+
def max(last: false)
|
|
1181
1459
|
n = rightmost(@root)
|
|
1182
|
-
n == @nil_node || n.value.empty?
|
|
1460
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1461
|
+
[n.key, last ? n.value.last : n.value.first]
|
|
1183
1462
|
end
|
|
1184
1463
|
|
|
1185
1464
|
def nearest(key)
|
|
1186
|
-
|
|
1465
|
+
n = find_nearest_node(key)
|
|
1466
|
+
n == @nil_node || n.value.empty? ? nil : [n.key, n.value.first]
|
|
1467
|
+
end
|
|
1468
|
+
|
|
1469
|
+
def prev(key, last: false)
|
|
1470
|
+
n = find_predecessor_node(key)
|
|
1471
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1472
|
+
[n.key, last ? n.value.last : n.value.first]
|
|
1473
|
+
end
|
|
1474
|
+
|
|
1475
|
+
def succ(key, last: false)
|
|
1476
|
+
n = find_successor_node(key)
|
|
1477
|
+
return nil if n == @nil_node || n.value.empty?
|
|
1478
|
+
[n.key, last ? n.value.last : n.value.first]
|
|
1187
1479
|
end
|
|
1188
1480
|
|
|
1189
1481
|
private
|
|
@@ -1273,6 +1565,65 @@ class MultiRBTree < RBTree
|
|
|
1273
1565
|
traverse_between(node.right, min, max, include_min, include_max, &block)
|
|
1274
1566
|
end
|
|
1275
1567
|
end
|
|
1568
|
+
|
|
1569
|
+
def traverse_lt_desc(node, key, &block)
|
|
1570
|
+
return if node == @nil_node
|
|
1571
|
+
|
|
1572
|
+
if (node.key <=> key) < 0
|
|
1573
|
+
traverse_lt_desc(node.right, key, &block)
|
|
1574
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1575
|
+
end
|
|
1576
|
+
traverse_lt_desc(node.left, key, &block)
|
|
1577
|
+
end
|
|
1578
|
+
|
|
1579
|
+
def traverse_lte_desc(node, key, &block)
|
|
1580
|
+
return if node == @nil_node
|
|
1581
|
+
|
|
1582
|
+
if (node.key <=> key) <= 0
|
|
1583
|
+
traverse_lte_desc(node.right, key, &block)
|
|
1584
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1585
|
+
end
|
|
1586
|
+
traverse_lte_desc(node.left, key, &block)
|
|
1587
|
+
end
|
|
1588
|
+
|
|
1589
|
+
def traverse_gt_desc(node, key, &block)
|
|
1590
|
+
return if node == @nil_node
|
|
1591
|
+
|
|
1592
|
+
traverse_gt_desc(node.right, key, &block)
|
|
1593
|
+
if (node.key <=> key) > 0
|
|
1594
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1595
|
+
traverse_gt_desc(node.left, key, &block)
|
|
1596
|
+
end
|
|
1597
|
+
end
|
|
1598
|
+
|
|
1599
|
+
def traverse_gte_desc(node, key, &block)
|
|
1600
|
+
return if node == @nil_node
|
|
1601
|
+
|
|
1602
|
+
traverse_gte_desc(node.right, key, &block)
|
|
1603
|
+
if (node.key <=> key) >= 0
|
|
1604
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1605
|
+
traverse_gte_desc(node.left, key, &block)
|
|
1606
|
+
end
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
def traverse_between_desc(node, min, max, include_min, include_max, &block)
|
|
1610
|
+
return if node == @nil_node
|
|
1611
|
+
|
|
1612
|
+
if (node.key <=> max) < 0
|
|
1613
|
+
traverse_between_desc(node.right, min, max, include_min, include_max, &block)
|
|
1614
|
+
end
|
|
1615
|
+
|
|
1616
|
+
greater = include_min ? (node.key <=> min) >= 0 : (node.key <=> min) > 0
|
|
1617
|
+
less = include_max ? (node.key <=> max) <= 0 : (node.key <=> max) < 0
|
|
1618
|
+
|
|
1619
|
+
if greater && less
|
|
1620
|
+
node.value.reverse_each { |v| yield node.key, v }
|
|
1621
|
+
end
|
|
1622
|
+
|
|
1623
|
+
if (node.key <=> min) > 0
|
|
1624
|
+
traverse_between_desc(node.left, min, max, include_min, include_max, &block)
|
|
1625
|
+
end
|
|
1626
|
+
end
|
|
1276
1627
|
end
|
|
1277
1628
|
|
|
1278
1629
|
# 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.8
|
|
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: []
|