rbtree-ruby 0.3.4 → 0.3.5
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 +12 -0
- data/README.ja.md +2 -3
- data/README.md +2 -3
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +292 -285
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d89cd253c189b3342f30e2a4df3a293561e2353af6592d86f25fcea5eeb76433
|
|
4
|
+
data.tar.gz: 9af04bff3851e7806fc637c2fc0ad583decf8e7c020edf841220a2b90fe8aaf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9708ffb8b421f883953c68582c607b9333e671542e21c0f3e0927838d2bb68202142150c11dd11541ea79fa1fcafc6c3195456c38d4b0db6b3897272d205835
|
|
7
|
+
data.tar.gz: 601190e616b7b478cc6ceabe682e9d43ef169871753667fe61e326f65daceaa60441122a550f8458f1d4a7a794a032ac0add88f326956db903854ee96e34b613
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.5] - 2026-01-26
|
|
9
|
+
|
|
10
|
+
### Optimized
|
|
11
|
+
- **O(1) Max Access**: Added cached `@max_node` to ensure `max` and `pop` operations run in constant time.
|
|
12
|
+
- **Traversal Engine Rewrite**: Optimized all range queries (`lt`, `gt`, `between`, `each`) with a unified traversal engine featuring $O(1)$ range rejection and bound optimization.
|
|
13
|
+
- **Fast-path Startup**: Range queries now leverage the hybrid Hash index to jump directly to target nodes (even for exclusive bounds), eliminating $O(\log N)$ tree descents when keys exist.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Defensive Assertion**: Added an explicit check to `find_nearest_node` to ensure only `Numeric` keys are accepted, formalizing a pre-existing design constraint.
|
|
17
|
+
- **Internal Refactoring**: Unified the insertion logic between `RBTree` and `MultiRBTree` into a single internal method to improve maintainability and ensure consistent optimization.
|
|
18
|
+
|
|
8
19
|
## [0.3.4] - 2026-01-25
|
|
9
20
|
|
|
10
21
|
### Added
|
|
@@ -219,6 +230,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
219
230
|
- ASCII diagrams for tree rotation operations
|
|
220
231
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
221
232
|
|
|
233
|
+
[0.3.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.5
|
|
222
234
|
[0.3.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.4
|
|
223
235
|
[0.3.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.3
|
|
224
236
|
[0.3.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.2
|
data/README.ja.md
CHANGED
|
@@ -239,10 +239,9 @@ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
|
|
|
239
239
|
- `delete(key)` - O(log n)
|
|
240
240
|
- `value(key)` / `[]` - **O(1)** (内部ハッシュインデックスによる超高速アクセス)
|
|
241
241
|
- `has_key?` - **O(1)** (内部ハッシュインデックスによる超高速チェック)
|
|
242
|
-
- `min` - **O(1)**
|
|
243
|
-
- `max` - O(log n)
|
|
242
|
+
- `min` / `max` - **O(1)**
|
|
244
243
|
- `shift` / `pop` - O(log n)
|
|
245
|
-
- `prev` / `succ` - O(log n)、O(1)
|
|
244
|
+
- `prev` / `succ` - O(log n)、O(1)ハッシュチェックと高速な走査開始により改善
|
|
246
245
|
|
|
247
246
|
全要素のイテレーションはO(n)時間。
|
|
248
247
|
|
data/README.md
CHANGED
|
@@ -239,10 +239,9 @@ All major operations run in **O(log n)** time:
|
|
|
239
239
|
- `delete(key)` - O(log n)
|
|
240
240
|
- `value(key)` / `[]` - **O(1)** (hybrid hash index)
|
|
241
241
|
- `has_key?` - **O(1)** (hybrid hash index)
|
|
242
|
-
- `min` - **O(1)**
|
|
243
|
-
- `max` - O(log n)
|
|
242
|
+
- `min` / `max` - **O(1)**
|
|
244
243
|
- `shift` / `pop` - O(log n)
|
|
245
|
-
- `prev` / `succ` - O(log n) with O(1) hash check
|
|
244
|
+
- `prev` / `succ` - O(log n) with O(1) hash check and faster startup
|
|
246
245
|
|
|
247
246
|
Iteration over all elements takes O(n) time.
|
|
248
247
|
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -99,6 +99,7 @@ class RBTree
|
|
|
99
99
|
@nil_node.right = @nil_node
|
|
100
100
|
@root = @nil_node
|
|
101
101
|
@min_node = @nil_node
|
|
102
|
+
@max_node = @nil_node
|
|
102
103
|
@hash_index = {} # Hash index for O(1) key lookup
|
|
103
104
|
@node_allocator = node_allocator
|
|
104
105
|
@key_count = 0
|
|
@@ -418,7 +419,7 @@ class RBTree
|
|
|
418
419
|
# tree.pop # => [3, "three"]
|
|
419
420
|
# tree.pop # => [2, "two"]
|
|
420
421
|
def pop
|
|
421
|
-
return nil unless (n =
|
|
422
|
+
return nil unless (n = @max_node) != @nil_node
|
|
422
423
|
pair = n.pair
|
|
423
424
|
delete(n.key)
|
|
424
425
|
pair
|
|
@@ -428,7 +429,7 @@ class RBTree
|
|
|
428
429
|
#
|
|
429
430
|
# @return [RBTree] self
|
|
430
431
|
def clear
|
|
431
|
-
@root = @min_node = @nil_node
|
|
432
|
+
@root = @min_node = @max_node = @nil_node
|
|
432
433
|
@hash_index.clear
|
|
433
434
|
@key_count = 0
|
|
434
435
|
self
|
|
@@ -484,11 +485,7 @@ class RBTree
|
|
|
484
485
|
# end
|
|
485
486
|
def each(reverse: false, safe: false, &block)
|
|
486
487
|
return enum_for(__method__, reverse: reverse, safe: safe) { size } unless block_given?
|
|
487
|
-
|
|
488
|
-
traverse_all_desc(@root, safe: safe, &block)
|
|
489
|
-
else
|
|
490
|
-
traverse_all_asc(@root, safe: safe, &block)
|
|
491
|
-
end
|
|
488
|
+
traverse_range(reverse, nil, nil, false, false, safe: safe, &block)
|
|
492
489
|
self
|
|
493
490
|
end
|
|
494
491
|
|
|
@@ -530,11 +527,7 @@ class RBTree
|
|
|
530
527
|
# tree.lt(3, safe: true) { |k, _| tree.delete(k) if k.even? } # safe to delete
|
|
531
528
|
def lt(key, reverse: false, safe: false, &block)
|
|
532
529
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
533
|
-
|
|
534
|
-
traverse_lt_desc(@root, key, safe: safe, &block)
|
|
535
|
-
else
|
|
536
|
-
traverse_lt_asc(@root, key, safe: safe, &block)
|
|
537
|
-
end
|
|
530
|
+
traverse_range(reverse, nil, key, false, false, safe: safe, &block)
|
|
538
531
|
self
|
|
539
532
|
end
|
|
540
533
|
|
|
@@ -551,11 +544,7 @@ class RBTree
|
|
|
551
544
|
# tree.lte(3, reverse: true).first # => [3, "three"]
|
|
552
545
|
def lte(key, reverse: false, safe: false, &block)
|
|
553
546
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
554
|
-
|
|
555
|
-
traverse_lte_desc(@root, key, safe: safe, &block)
|
|
556
|
-
else
|
|
557
|
-
traverse_lte_asc(@root, key, safe: safe, &block)
|
|
558
|
-
end
|
|
547
|
+
traverse_range(reverse, nil, key, false, true, safe: safe, &block)
|
|
559
548
|
self
|
|
560
549
|
end
|
|
561
550
|
|
|
@@ -572,11 +561,7 @@ class RBTree
|
|
|
572
561
|
# tree.gt(2, reverse: true).first # => [4, "four"]
|
|
573
562
|
def gt(key, reverse: false, safe: false, &block)
|
|
574
563
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
575
|
-
|
|
576
|
-
traverse_gt_desc(@root, key, safe: safe, &block)
|
|
577
|
-
else
|
|
578
|
-
traverse_gt_asc(@root, key, safe: safe, &block)
|
|
579
|
-
end
|
|
564
|
+
traverse_range(reverse, key, nil, false, false, safe: safe, &block)
|
|
580
565
|
self
|
|
581
566
|
end
|
|
582
567
|
|
|
@@ -593,11 +578,7 @@ class RBTree
|
|
|
593
578
|
# tree.gte(2, reverse: true).first # => [4, "four"]
|
|
594
579
|
def gte(key, reverse: false, safe: false, &block)
|
|
595
580
|
return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
|
|
596
|
-
|
|
597
|
-
traverse_gte_desc(@root, key, safe: safe, &block)
|
|
598
|
-
else
|
|
599
|
-
traverse_gte_asc(@root, key, safe: safe, &block)
|
|
600
|
-
end
|
|
581
|
+
traverse_range(reverse, key, nil, true, false, safe: safe, &block)
|
|
601
582
|
self
|
|
602
583
|
end
|
|
603
584
|
|
|
@@ -617,11 +598,7 @@ class RBTree
|
|
|
617
598
|
# tree.between(2, 4, reverse: true).first # => [4, "four"]
|
|
618
599
|
def between(min, max, include_min: true, include_max: true, reverse: false, safe: false, &block)
|
|
619
600
|
return enum_for(__method__, min, max, include_min: include_min, include_max: include_max, reverse: reverse, safe: safe) unless block_given?
|
|
620
|
-
|
|
621
|
-
traverse_between_desc(@root, min, max, include_min, include_max, safe: safe, &block)
|
|
622
|
-
else
|
|
623
|
-
traverse_between_asc(@root, min, max, include_min, include_max, safe: safe, &block)
|
|
624
|
-
end
|
|
601
|
+
traverse_range(reverse, min, max, include_min, include_max, safe: safe, &block)
|
|
625
602
|
self
|
|
626
603
|
end
|
|
627
604
|
|
|
@@ -655,9 +632,8 @@ class RBTree
|
|
|
655
632
|
# @!visibility private
|
|
656
633
|
private
|
|
657
634
|
|
|
658
|
-
def min_node = (
|
|
659
|
-
|
|
660
|
-
def max_node = ((n = rightmost(@root)) == @nil_node) ? nil : n
|
|
635
|
+
def min_node = (@min_node == @nil_node) ? nil : @min_node
|
|
636
|
+
def max_node = (@max_node == @nil_node) ? nil : @max_node
|
|
661
637
|
|
|
662
638
|
# Inserts a single key-value pair.
|
|
663
639
|
#
|
|
@@ -666,27 +642,51 @@ class RBTree
|
|
|
666
642
|
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
667
643
|
# @return [Boolean, nil] true if inserted/updated, nil if key exists and overwrite is false
|
|
668
644
|
def insert_entry(key, value, overwrite: true)
|
|
645
|
+
insert_entry_generic(key) do |node, is_new|
|
|
646
|
+
if is_new
|
|
647
|
+
value
|
|
648
|
+
else
|
|
649
|
+
if overwrite
|
|
650
|
+
node.value = value
|
|
651
|
+
true
|
|
652
|
+
else
|
|
653
|
+
nil
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# Generic entry insertion logic shared between RBTree and MultiRBTree.
|
|
660
|
+
#
|
|
661
|
+
# @param key [Object] the key to insert
|
|
662
|
+
# @yield [node, is_new] yields the existing node (if any) and whether it's a new insertion
|
|
663
|
+
# @yieldparam node [Node, nil] the existing node or nil
|
|
664
|
+
# @yieldparam is_new [Boolean] true if no node with the key exists
|
|
665
|
+
# @yieldreturn [Object]
|
|
666
|
+
# - if is_new: the initial value for the new node
|
|
667
|
+
# - if !is_new: the value to return from insert_entry
|
|
668
|
+
def insert_entry_generic(key)
|
|
669
669
|
if (node = @hash_index[key])
|
|
670
|
-
return
|
|
671
|
-
node.value = value
|
|
672
|
-
return true
|
|
670
|
+
return yield(node, false)
|
|
673
671
|
end
|
|
672
|
+
|
|
674
673
|
y = @nil_node
|
|
675
674
|
x = @root
|
|
676
675
|
while x != @nil_node
|
|
677
676
|
y = x
|
|
678
677
|
cmp = key <=> x.key
|
|
679
678
|
if cmp == 0
|
|
680
|
-
return
|
|
681
|
-
x.value = value
|
|
682
|
-
return true
|
|
679
|
+
return yield(x, false)
|
|
683
680
|
elsif cmp < 0
|
|
684
681
|
x = x.left
|
|
685
682
|
else
|
|
686
683
|
x = x.right
|
|
687
684
|
end
|
|
688
685
|
end
|
|
689
|
-
|
|
686
|
+
|
|
687
|
+
initial_value = yield(nil, true)
|
|
688
|
+
|
|
689
|
+
z = allocate_node(key, initial_value, Node::RED, @nil_node, @nil_node, @nil_node)
|
|
690
690
|
z.parent = y
|
|
691
691
|
if y == @nil_node
|
|
692
692
|
@root = z
|
|
@@ -703,14 +703,34 @@ class RBTree
|
|
|
703
703
|
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
704
704
|
@min_node = z
|
|
705
705
|
end
|
|
706
|
+
if @max_node == @nil_node || (key <=> @max_node.key) > 0
|
|
707
|
+
@max_node = z
|
|
708
|
+
end
|
|
706
709
|
|
|
707
|
-
@hash_index[key] = z
|
|
710
|
+
@hash_index[key] = z
|
|
708
711
|
true
|
|
709
712
|
end
|
|
710
713
|
|
|
714
|
+
# Traverses the tree in a specified direction (ascending or descending).
|
|
715
|
+
#
|
|
716
|
+
# @param direction [Boolean] true for descending, false for ascending
|
|
717
|
+
# @param min [Object] the lower bound (inclusive)
|
|
718
|
+
# @param max [Object] the upper bound (inclusive)
|
|
719
|
+
# @param include_min [Boolean] whether to include the lower bound
|
|
720
|
+
# @param include_max [Boolean] whether to include the upper bound
|
|
721
|
+
# @param safe [Boolean] whether to use safe traversal
|
|
722
|
+
# @yield [key, value] each key-value pair in the specified direction
|
|
723
|
+
# @return [void]
|
|
724
|
+
def traverse_range(direction, min, max, include_min, include_max, safe: false, &block)
|
|
725
|
+
if direction
|
|
726
|
+
traverse_range_desc(min, max, include_min, include_max, safe: safe, &block)
|
|
727
|
+
else
|
|
728
|
+
traverse_range_asc(min, max, include_min, include_max, safe: safe, &block)
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
711
732
|
# Traverses the tree in ascending order (in-order traversal).
|
|
712
733
|
#
|
|
713
|
-
# @param node [Node] the current node
|
|
714
734
|
# @param min [Object] the lower bound (inclusive)
|
|
715
735
|
# @param max [Object] the upper bound (inclusive)
|
|
716
736
|
# @param include_min [Boolean] whether to include the lower bound
|
|
@@ -718,56 +738,31 @@ class RBTree
|
|
|
718
738
|
# @param safe [Boolean] whether to use safe traversal
|
|
719
739
|
# @yield [key, value] each key-value pair in ascending order
|
|
720
740
|
# @return [void]
|
|
721
|
-
def traverse_range_asc(
|
|
741
|
+
def traverse_range_asc(min, max, include_min, include_max, safe: false, &block)
|
|
742
|
+
# O(1) Range Rejection
|
|
743
|
+
return if min && @max_node != @nil_node && (min <=> @max_node.key) > 0
|
|
744
|
+
return if min && @max_node != @nil_node && !include_min && (min <=> @max_node.key) == 0
|
|
745
|
+
|
|
746
|
+
# O(1) Bound Optimization
|
|
747
|
+
max = nil if max && @max_node != @nil_node && (cmp = max <=> @max_node.key) >= 0 && (cmp > 0 || include_max)
|
|
748
|
+
|
|
722
749
|
if safe
|
|
723
750
|
pair = !min ? find_min :
|
|
724
751
|
include_min && @hash_index[min]&.pair || find_successor(min)
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
pair = find_successor(current_key)
|
|
730
|
-
end
|
|
731
|
-
else
|
|
732
|
-
while pair && pair[0] < max
|
|
733
|
-
current_key = pair[0]
|
|
734
|
-
yield pair
|
|
735
|
-
pair = find_successor(current_key)
|
|
736
|
-
end
|
|
752
|
+
while pair && (!max || pair[0] < max)
|
|
753
|
+
current_key = pair[0]
|
|
754
|
+
yield pair
|
|
755
|
+
pair = find_successor(current_key)
|
|
737
756
|
end
|
|
738
757
|
yield pair if pair && max && include_max && pair[0] == max
|
|
739
758
|
else
|
|
740
|
-
stack =
|
|
741
|
-
|
|
742
|
-
while current != @nil_node || !stack.empty?
|
|
743
|
-
while current != @nil_node
|
|
744
|
-
if min && ((current.key <=> min) < 0 ||
|
|
745
|
-
(!include_min && (current.key <=> min) == 0))
|
|
746
|
-
current = current.right
|
|
747
|
-
else
|
|
748
|
-
stack << current
|
|
749
|
-
current = current.left
|
|
750
|
-
end
|
|
751
|
-
end
|
|
752
|
-
|
|
753
|
-
if !stack.empty?
|
|
754
|
-
current = stack.pop
|
|
755
|
-
|
|
756
|
-
if max && ((current.key <=> max) > 0 ||
|
|
757
|
-
(!include_max && (current.key <=> max) == 0))
|
|
758
|
-
return
|
|
759
|
-
else
|
|
760
|
-
yield current.pair
|
|
761
|
-
current = current.right
|
|
762
|
-
end
|
|
763
|
-
end
|
|
764
|
-
end
|
|
759
|
+
start_node, stack = resolve_startup_asc(min, include_min)
|
|
760
|
+
traverse_from_asc(start_node, stack, max, include_max, &block)
|
|
765
761
|
end
|
|
766
762
|
end
|
|
767
763
|
|
|
768
764
|
# Traverses the tree in descending order (reverse in-order traversal).
|
|
769
765
|
#
|
|
770
|
-
# @param node [Node] the current node
|
|
771
766
|
# @param min [Object] the lower bound (inclusive)
|
|
772
767
|
# @param max [Object] the upper bound (inclusive)
|
|
773
768
|
# @param include_min [Boolean] whether to include the lower bound
|
|
@@ -775,164 +770,218 @@ class RBTree
|
|
|
775
770
|
# @param safe [Boolean] whether to use safe traversal
|
|
776
771
|
# @yield [key, value] each key-value pair in descending order
|
|
777
772
|
# @return [void]
|
|
778
|
-
def traverse_range_desc(
|
|
773
|
+
def traverse_range_desc(min, max, include_min, include_max, safe: false, &block)
|
|
774
|
+
# O(1) Range Rejection
|
|
775
|
+
return if max && @min_node != @nil_node && (max <=> @min_node.key) < 0
|
|
776
|
+
return if max && @min_node != @nil_node && !include_max && (max <=> @min_node.key) == 0
|
|
777
|
+
|
|
778
|
+
# O(1) Bound Optimization
|
|
779
|
+
min = nil if min && @min_node != @nil_node && (cmp = min <=> @min_node.key) <= 0 && (cmp < 0 || include_min)
|
|
780
|
+
|
|
779
781
|
if safe
|
|
780
782
|
pair = !max ? find_max :
|
|
781
783
|
include_max && @hash_index[max]&.pair || find_predecessor(max)
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
pair = find_predecessor(current_key)
|
|
787
|
-
end
|
|
788
|
-
else
|
|
789
|
-
while pair && pair[0] > min
|
|
790
|
-
current_key = pair[0]
|
|
791
|
-
yield pair
|
|
792
|
-
pair = find_predecessor(current_key)
|
|
793
|
-
end
|
|
784
|
+
while pair && (!min || pair[0] > min)
|
|
785
|
+
current_key = pair[0]
|
|
786
|
+
yield pair
|
|
787
|
+
pair = find_predecessor(current_key)
|
|
794
788
|
end
|
|
795
789
|
yield pair if pair && min && include_min && pair[0] == min
|
|
796
790
|
else
|
|
797
|
-
stack =
|
|
798
|
-
|
|
799
|
-
while current != @nil_node || !stack.empty?
|
|
800
|
-
while current != @nil_node
|
|
801
|
-
if max && ((current.key <=> max) > 0 ||
|
|
802
|
-
(!include_max && (current.key <=> max) == 0))
|
|
803
|
-
current = current.left
|
|
804
|
-
else
|
|
805
|
-
stack << current
|
|
806
|
-
current = current.right
|
|
807
|
-
end
|
|
808
|
-
end
|
|
809
|
-
|
|
810
|
-
if !stack.empty?
|
|
811
|
-
current = stack.pop
|
|
812
|
-
|
|
813
|
-
if min && ((current.key <=> min) < 0 ||
|
|
814
|
-
(!include_min && (current.key <=> min) == 0))
|
|
815
|
-
return
|
|
816
|
-
else
|
|
817
|
-
yield current.pair
|
|
818
|
-
current = current.left
|
|
819
|
-
end
|
|
820
|
-
end
|
|
821
|
-
end
|
|
791
|
+
start_node, stack = resolve_startup_desc(max, include_max)
|
|
792
|
+
traverse_from_desc(start_node, stack, min, include_min, &block)
|
|
822
793
|
end
|
|
823
794
|
end
|
|
824
795
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
#
|
|
828
|
-
# @yield [key, value] each key-value pair in ascending order
|
|
829
|
-
# @return [void]
|
|
830
|
-
def traverse_all_asc(node, safe: false, &block) =
|
|
831
|
-
traverse_range_asc(node, nil, nil, false, false, safe: safe, &block)
|
|
832
|
-
|
|
833
|
-
# Traverses the tree in descending order (reverse in-order traversal).
|
|
834
|
-
#
|
|
835
|
-
# @param node [Node] the current node
|
|
836
|
-
# @yield [key, value] each key-value pair in descending order
|
|
837
|
-
# @return [void]
|
|
838
|
-
def traverse_all_desc(node, safe: false, &block) =
|
|
839
|
-
traverse_range_desc(node, nil, nil, false, false, safe: safe, &block)
|
|
840
|
-
|
|
841
|
-
# Traverses nodes with keys less than the specified key.
|
|
796
|
+
private
|
|
797
|
+
|
|
798
|
+
# Returns the predecessor of the given node.
|
|
842
799
|
#
|
|
843
|
-
# @param node [Node] the
|
|
844
|
-
# @
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
800
|
+
# @param node [Node] the node to find the predecessor of
|
|
801
|
+
# @return [Node] the predecessor node
|
|
802
|
+
def predecessor_node_of(node)
|
|
803
|
+
if node.left != @nil_node
|
|
804
|
+
return rightmost(node.left)
|
|
805
|
+
end
|
|
806
|
+
y = node.parent
|
|
807
|
+
while y != @nil_node && node == y.left
|
|
808
|
+
node = y
|
|
809
|
+
y = y.parent
|
|
810
|
+
end
|
|
811
|
+
y
|
|
812
|
+
end
|
|
849
813
|
|
|
850
|
-
#
|
|
814
|
+
# Returns the successor of the given node.
|
|
851
815
|
#
|
|
852
|
-
# @param node [Node] the
|
|
853
|
-
# @
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
816
|
+
# @param node [Node] the node to find the successor of
|
|
817
|
+
# @return [Node] the successor node
|
|
818
|
+
def successor_node_of(node)
|
|
819
|
+
if node.right != @nil_node
|
|
820
|
+
return leftmost(node.right)
|
|
821
|
+
end
|
|
822
|
+
y = node.parent
|
|
823
|
+
while y != @nil_node && node == y.right
|
|
824
|
+
node = y
|
|
825
|
+
y = y.parent
|
|
826
|
+
end
|
|
827
|
+
y
|
|
828
|
+
end
|
|
858
829
|
|
|
859
|
-
#
|
|
830
|
+
# Resolves the startup node for ascending traversal.
|
|
860
831
|
#
|
|
861
|
-
# @param
|
|
862
|
-
# @param
|
|
863
|
-
# @
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
832
|
+
# @param min [Object] the minimum key to traverse from
|
|
833
|
+
# @param include_min [Boolean] whether to include the minimum key
|
|
834
|
+
# @return [Array(Node, Array)] the starting node and stack
|
|
835
|
+
def resolve_startup_asc(min, include_min)
|
|
836
|
+
# 1. Use cached min if no lower bound or if min is less than tree min
|
|
837
|
+
if @min_node != @nil_node && (!min || (min <=> @min_node.key) < 0)
|
|
838
|
+
return [@min_node, reconstruct_stack_asc(@min_node)]
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
# 2. Use Hash index if key exists
|
|
842
|
+
if min && (node = @hash_index[min])
|
|
843
|
+
start_node = include_min ? node : successor_node_of(node)
|
|
844
|
+
return [start_node, reconstruct_stack_asc(start_node)]
|
|
845
|
+
end
|
|
867
846
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
847
|
+
# 3. Fallback to tree search from root
|
|
848
|
+
stack = []
|
|
849
|
+
current = @root
|
|
850
|
+
while current != @nil_node
|
|
851
|
+
if min && ((current.key <=> min) < 0 || (!include_min && (current.key <=> min) == 0))
|
|
852
|
+
current = current.right
|
|
853
|
+
else
|
|
854
|
+
stack << current
|
|
855
|
+
current = current.left
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
[@nil_node, stack]
|
|
859
|
+
end
|
|
876
860
|
|
|
877
|
-
#
|
|
878
|
-
#
|
|
879
|
-
# @param node [Node] the
|
|
880
|
-
# @
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
861
|
+
# Reconstructs the stack for ascending traversal.
|
|
862
|
+
#
|
|
863
|
+
# @param node [Node] the node to reconstruct the stack from
|
|
864
|
+
# @return [Array] the reconstructed stack
|
|
865
|
+
def reconstruct_stack_asc(node)
|
|
866
|
+
return [] if node == @nil_node
|
|
867
|
+
stack = []
|
|
868
|
+
curr = node
|
|
869
|
+
while (p = curr.parent) != @nil_node
|
|
870
|
+
stack << p if curr == p.left
|
|
871
|
+
curr = p
|
|
872
|
+
end
|
|
873
|
+
stack.reverse!
|
|
874
|
+
stack
|
|
875
|
+
end
|
|
888
876
|
|
|
889
|
-
# Traverses
|
|
877
|
+
# Traverses the tree in ascending order.
|
|
890
878
|
#
|
|
891
|
-
# @param
|
|
892
|
-
# @param
|
|
893
|
-
# @
|
|
894
|
-
# @
|
|
895
|
-
|
|
896
|
-
|
|
879
|
+
# @param current [Node] the current node
|
|
880
|
+
# @param stack [Array] the stack of nodes to traverse
|
|
881
|
+
# @param max [Object] the maximum key to traverse to
|
|
882
|
+
# @param include_max [Boolean] whether to include the maximum key
|
|
883
|
+
# @yield [Array(Object, Object)] each key-value pair
|
|
884
|
+
# @yieldparam key [Object] the key
|
|
885
|
+
# @yieldparam val [Object] the value
|
|
886
|
+
def traverse_from_asc(current, stack, max, include_max, &block)
|
|
887
|
+
while current != @nil_node || !stack.empty?
|
|
888
|
+
if current != @nil_node
|
|
889
|
+
if max
|
|
890
|
+
cmp = current.key <=> max
|
|
891
|
+
if cmp >= 0
|
|
892
|
+
yield current.pair if include_max && cmp == 0
|
|
893
|
+
return
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
yield current.pair
|
|
897
|
+
current = current.right
|
|
898
|
+
while current != @nil_node
|
|
899
|
+
stack << current
|
|
900
|
+
current = current.left
|
|
901
|
+
end
|
|
902
|
+
else
|
|
903
|
+
current = stack.pop
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
end
|
|
897
907
|
|
|
898
|
-
#
|
|
908
|
+
# Resolves the startup node for descending traversal.
|
|
899
909
|
#
|
|
900
|
-
# @param
|
|
901
|
-
# @param
|
|
902
|
-
# @
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
910
|
+
# @param max [Object] the maximum key to traverse from
|
|
911
|
+
# @param include_max [Boolean] whether to include the maximum key
|
|
912
|
+
# @return [Array(Node, Array)] the starting node and stack
|
|
913
|
+
def resolve_startup_desc(max, include_max)
|
|
914
|
+
# 1. Use cached max if no upper bound or if max is greater than tree max
|
|
915
|
+
if @max_node != @nil_node && (!max || (max <=> @max_node.key) > 0)
|
|
916
|
+
return [@max_node, reconstruct_stack_desc(@max_node)]
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# 2. Use Hash index if key exists
|
|
920
|
+
if max && (node = @hash_index[max])
|
|
921
|
+
start_node = include_max ? node : predecessor_node_of(node)
|
|
922
|
+
return [start_node, reconstruct_stack_desc(start_node)]
|
|
923
|
+
end
|
|
906
924
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
925
|
+
# 3. Fallback to tree search from root
|
|
926
|
+
stack = []
|
|
927
|
+
current = @root
|
|
928
|
+
while current != @nil_node
|
|
929
|
+
if max && ((current.key <=> max) > 0 || (!include_max && (current.key <=> max) == 0))
|
|
930
|
+
current = current.left
|
|
931
|
+
else
|
|
932
|
+
stack << current
|
|
933
|
+
current = current.right
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
[@nil_node, stack]
|
|
937
|
+
end
|
|
915
938
|
|
|
916
|
-
#
|
|
917
|
-
#
|
|
918
|
-
# @param node [Node] the
|
|
919
|
-
# @
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
939
|
+
# Reconstructs the stack for descending traversal.
|
|
940
|
+
#
|
|
941
|
+
# @param node [Node] the node to reconstruct the stack from
|
|
942
|
+
# @return [Array] the reconstructed stack
|
|
943
|
+
def reconstruct_stack_desc(node)
|
|
944
|
+
return [] if node == @nil_node
|
|
945
|
+
stack = []
|
|
946
|
+
curr = node
|
|
947
|
+
while (p = curr.parent) != @nil_node
|
|
948
|
+
stack << p if curr == p.right
|
|
949
|
+
curr = p
|
|
950
|
+
end
|
|
951
|
+
stack.reverse!
|
|
952
|
+
stack
|
|
953
|
+
end
|
|
924
954
|
|
|
925
|
-
# Traverses
|
|
955
|
+
# Traverses the tree in descending order.
|
|
926
956
|
#
|
|
927
|
-
# @param
|
|
928
|
-
# @param
|
|
929
|
-
# @param
|
|
930
|
-
# @param include_min [Boolean] whether to include the
|
|
931
|
-
# @
|
|
932
|
-
# @
|
|
933
|
-
# @
|
|
934
|
-
def
|
|
935
|
-
|
|
957
|
+
# @param current [Node] the current node
|
|
958
|
+
# @param stack [Array] the stack of nodes to traverse
|
|
959
|
+
# @param min [Object] the minimum key to traverse to
|
|
960
|
+
# @param include_min [Boolean] whether to include the minimum key
|
|
961
|
+
# @yield [Array(Object, Object)] each key-value pair
|
|
962
|
+
# @yieldparam key [Object] the key
|
|
963
|
+
# @yieldparam val [Object] the value
|
|
964
|
+
def traverse_from_desc(current, stack, min, include_min, &block)
|
|
965
|
+
while current != @nil_node || !stack.empty?
|
|
966
|
+
if current != @nil_node
|
|
967
|
+
if min
|
|
968
|
+
cmp = current.key <=> min
|
|
969
|
+
if cmp <= 0
|
|
970
|
+
yield current.pair if include_min && cmp == 0
|
|
971
|
+
return
|
|
972
|
+
end
|
|
973
|
+
end
|
|
974
|
+
yield current.pair
|
|
975
|
+
current = current.left
|
|
976
|
+
while current != @nil_node
|
|
977
|
+
stack << current
|
|
978
|
+
current = current.right
|
|
979
|
+
end
|
|
980
|
+
else
|
|
981
|
+
current = stack.pop
|
|
982
|
+
end
|
|
983
|
+
end
|
|
984
|
+
end
|
|
936
985
|
|
|
937
986
|
# Restores red-black tree properties after insertion.
|
|
938
987
|
#
|
|
@@ -1036,6 +1085,10 @@ class RBTree
|
|
|
1036
1085
|
if next_min_node
|
|
1037
1086
|
@min_node = next_min_node
|
|
1038
1087
|
end
|
|
1088
|
+
if z == @max_node
|
|
1089
|
+
next_max_node = predecessor_node_of(z)
|
|
1090
|
+
@max_node = next_max_node
|
|
1091
|
+
end
|
|
1039
1092
|
|
|
1040
1093
|
value = z.value
|
|
1041
1094
|
release_node(z)
|
|
@@ -1128,6 +1181,13 @@ class RBTree
|
|
|
1128
1181
|
# @param key [Numeric] the target key
|
|
1129
1182
|
# @return [Node] the nearest node, or @nil_node if tree is empty
|
|
1130
1183
|
def find_nearest_node(key)
|
|
1184
|
+
raise ArgumentError, "key must be Numeric" unless key.is_a?(Numeric)
|
|
1185
|
+
|
|
1186
|
+
# If key is larger than max_key, return max_node
|
|
1187
|
+
return @max_node if @max_node != @nil_node && key >= @max_node.key
|
|
1188
|
+
# If key is smaller than min_key, return min_node
|
|
1189
|
+
return @min_node if @min_node != @nil_node && key <= @min_node.key
|
|
1190
|
+
|
|
1131
1191
|
current = @root
|
|
1132
1192
|
closest = @nil_node
|
|
1133
1193
|
min_dist = nil
|
|
@@ -1168,21 +1228,11 @@ class RBTree
|
|
|
1168
1228
|
# @param key [Object] the reference key
|
|
1169
1229
|
# @return [Node] the predecessor node, or @nil_node if none exists
|
|
1170
1230
|
def find_predecessor_node(key)
|
|
1171
|
-
#
|
|
1231
|
+
# If key is larger than max_key, return max_node
|
|
1232
|
+
return @max_node if max_key && (key <=> max_key) > 0
|
|
1233
|
+
# If key exists using O(1) hash lookup, return predecessor node
|
|
1172
1234
|
if (node = @hash_index[key])
|
|
1173
|
-
|
|
1174
|
-
if node.left != @nil_node
|
|
1175
|
-
return rightmost(node.left)
|
|
1176
|
-
else
|
|
1177
|
-
# Walk up to find first ancestor where we came from the right
|
|
1178
|
-
current = node
|
|
1179
|
-
parent = current.parent
|
|
1180
|
-
while parent != @nil_node && current == parent.left
|
|
1181
|
-
current = parent
|
|
1182
|
-
parent = parent.parent
|
|
1183
|
-
end
|
|
1184
|
-
return parent
|
|
1185
|
-
end
|
|
1235
|
+
return predecessor_node_of(node)
|
|
1186
1236
|
end
|
|
1187
1237
|
|
|
1188
1238
|
# Key doesn't exist: descend tree tracking the best candidate
|
|
@@ -1218,25 +1268,14 @@ class RBTree
|
|
|
1218
1268
|
# @param key [Object] the reference key
|
|
1219
1269
|
# @return [Node] the successor node, or @nil_node if none exists
|
|
1220
1270
|
def find_successor_node(key)
|
|
1271
|
+
# If key is larger than or equal to max_key, return nil
|
|
1272
|
+
return @nil_node if max_key && (key <=> max_key) >= 0
|
|
1221
1273
|
# If key is smaller than min_key, return min_node
|
|
1222
|
-
return @min_node if min_key && key <
|
|
1223
|
-
#
|
|
1274
|
+
return @min_node if min_key && (key <=> min_key) < 0
|
|
1275
|
+
# If key exists using O(1) hash lookup, return successor node
|
|
1224
1276
|
if (node = @hash_index[key])
|
|
1225
|
-
|
|
1226
|
-
if node.right != @nil_node
|
|
1227
|
-
return leftmost(node.right)
|
|
1228
|
-
else
|
|
1229
|
-
# Walk up to find first ancestor where we came from the left
|
|
1230
|
-
current = node
|
|
1231
|
-
parent = current.parent
|
|
1232
|
-
while parent != @nil_node && current == parent.right
|
|
1233
|
-
current = parent
|
|
1234
|
-
parent = parent.parent
|
|
1235
|
-
end
|
|
1236
|
-
return parent
|
|
1237
|
-
end
|
|
1277
|
+
return successor_node_of(node)
|
|
1238
1278
|
end
|
|
1239
|
-
|
|
1240
1279
|
# Key doesn't exist: descend tree tracking the best candidate
|
|
1241
1280
|
current = @root
|
|
1242
1281
|
successor = @nil_node
|
|
@@ -1292,7 +1331,7 @@ class RBTree
|
|
|
1292
1331
|
# Returns the maximum key-value pair.
|
|
1293
1332
|
#
|
|
1294
1333
|
# @return [Array(Object, Object), nil] a two-element array [key, value], or nil if tree is empty
|
|
1295
|
-
def find_max = ((n =
|
|
1334
|
+
def find_max = ((n = @max_node) != @nil_node) && n.pair
|
|
1296
1335
|
|
|
1297
1336
|
# Performs a left rotation on the given node.
|
|
1298
1337
|
#
|
|
@@ -1696,47 +1735,15 @@ class MultiRBTree < RBTree
|
|
|
1696
1735
|
# tree.insert(1, 'first')
|
|
1697
1736
|
# tree.insert(1, 'second') # adds another value for key 1
|
|
1698
1737
|
def insert_entry(key, value, **)
|
|
1699
|
-
|
|
1700
|
-
node.value << value
|
|
1738
|
+
insert_entry_generic(key) do |node, is_new|
|
|
1701
1739
|
@value_count += 1
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
y = @nil_node
|
|
1705
|
-
x = @root
|
|
1706
|
-
while x != @nil_node
|
|
1707
|
-
y = x
|
|
1708
|
-
cmp = key <=> x.key
|
|
1709
|
-
if cmp == 0
|
|
1710
|
-
x.value << value
|
|
1711
|
-
@value_count += 1
|
|
1712
|
-
return true
|
|
1713
|
-
elsif cmp < 0
|
|
1714
|
-
x = x.left
|
|
1740
|
+
if is_new
|
|
1741
|
+
[value]
|
|
1715
1742
|
else
|
|
1716
|
-
|
|
1743
|
+
node.value << value
|
|
1744
|
+
true
|
|
1717
1745
|
end
|
|
1718
1746
|
end
|
|
1719
|
-
z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
|
|
1720
|
-
z.parent = y
|
|
1721
|
-
if y == @nil_node
|
|
1722
|
-
@root = z
|
|
1723
|
-
elsif (key <=> y.key) < 0
|
|
1724
|
-
y.left = z
|
|
1725
|
-
else
|
|
1726
|
-
y.right = z
|
|
1727
|
-
end
|
|
1728
|
-
z.left = @nil_node
|
|
1729
|
-
z.right = @nil_node
|
|
1730
|
-
z.color = Node::RED
|
|
1731
|
-
insert_fixup(z)
|
|
1732
|
-
@value_count += 1
|
|
1733
|
-
|
|
1734
|
-
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
1735
|
-
@min_node = z
|
|
1736
|
-
end
|
|
1737
|
-
|
|
1738
|
-
@hash_index[key] = z # Add to hash index
|
|
1739
|
-
true
|
|
1740
1747
|
end
|
|
1741
1748
|
|
|
1742
1749
|
# Traverses the tree in ascending order, yielding each key-value pair.
|