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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf6eb0a695ef93f89f9af8ceea5928c549785ea0110992f061b00ecdc76e3d64
4
- data.tar.gz: 458cbee1ded3e8e0686ec495392bffbaf881ac0bc798d68790240abb0a3011b7
3
+ metadata.gz: ad4ff4e15079d2034a111e0180f1abbf5f433283de5c125f45569f9b69f558fc
4
+ data.tar.gz: 7355e1ae3964e79aa8d110c52e38672641c8d67ac1cb4cfbbd974fe6298f07f7
5
5
  SHA512:
6
- metadata.gz: 48ee9755aad89802b6cb1b6af19a7919151e14531f2c6e2f2dfbb07062c5e1060490e8d5244d0fd960f44d4eb8b00e84ee9f24e5b3be6319c808f212dde848c2
7
- data.tar.gz: aeadf12c689069742c8cc05c569a84bbb6a87b3736f2f7e6351b68fb2aae5dbc24eca80e65a97455b50dae82b47c3ade7b8b70a171d86f879a179865eab9182d
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
- # RBTree
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 provides significant advantages for ordered operations:
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
- | `min` / `max` | O(1) / O(log n) | O(n) | **~1000x faster** |
157
- | Range queries (`between`, `lt`, `gt`) | O(log n + k) | O(n) | **10-100x faster** |
158
- | Nearest key search | O(log n) | O(n) | **100x+ faster** |
159
- | Ordered iteration | O(n), always sorted | Requires `sort` O(n log n) | **Free sorting** |
160
- | Key lookup | O(1) | O(1) | Equal |
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
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  class RBTree
4
4
  # The version of the rbtree-ruby gem
5
- VERSION = "0.1.6"
5
+ VERSION = "0.1.8"
6
6
  end
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 [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
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) { |k, v| puts "#{k}: #{v}" } # prints pairs and returns tree
338
- def lt(key, &block)
339
- if block_given?
340
- traverse_lt(@root, key, &block)
341
- self
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
- res = []
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 [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
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
- def lte(key, &block)
358
- if block_given?
359
- traverse_lte(@root, key, &block)
360
- self
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
- res = []
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 [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
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
- def gt(key, &block)
377
- if block_given?
378
- traverse_gt(@root, key, &block)
379
- self
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
- res = []
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 [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
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
- def gte(key, &block)
396
- if block_given?
397
- traverse_gte(@root, key, &block)
398
- self
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
- res = []
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 [Array<Array(Object, Object)>, RBTree] array of [key, value] pairs if no block, self otherwise
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, include_min: false) # => [[3, "three"], [4, "four"]]
418
- # tree.between(2, 4, include_max: false) # => [[2, "two"], [3, "three"]]
419
- def between(min, max, include_min: true, include_max: true, &block)
420
- if block_given?
421
- traverse_between(@root, min, max, include_min, include_max, &block)
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
- res = []
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
- # * min/max return first/last values respectively
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 # => 3 (total key-value pairs)
1007
- # tree.get(1) # => "first one" (first value)
1008
- # tree.get_all(1) # => ["first one", "second one"] (all values)
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) # removes only "first one"
1011
- # tree.get(1) # => "second one"
1252
+ # tree.delete_one(1) # removes only "first one"
1253
+ # tree.get(1) # => "second one"
1012
1254
  #
1013
- # tree.delete(1) # removes all remaining values for key 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 the first value associated with the given key.
1260
+ # Retrieves a value associated with the given key.
1019
1261
  #
1020
1262
  # @param key [Object] the key to look up
1021
- # @return [Object, nil] the first value for the key, or nil if not found
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) # => "first"
1027
- def get(key)
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? ? nil : n.value.first
1273
+ return nil if n == @nil_node || n.value.empty?
1274
+ last ? n.value.last : n.value.first
1030
1275
  end
1031
- alias_method :[], :get
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 only the first value for the specified key.
1367
+ # Deletes a single value for the specified key.
1103
1368
  #
1104
- # If the key has multiple values, removes only the first one.
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) # => "first"
1114
- # tree.get(1) # => "second"
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? ? nil : [n.key, n.value.first]
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
- (n = super(key)) ? [n[0], n[1].first] : nil
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.6
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: 3.6.9
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: []