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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 911e435d0a205afc1a62791f857ac66d6d6300d2aa332566aa916e65e16148d2
4
- data.tar.gz: d1a2b2aec3404901985cfec8a54c5b3df87d12bd4c8872833ba111dd3641033b
3
+ metadata.gz: d89cd253c189b3342f30e2a4df3a293561e2353af6592d86f25fcea5eeb76433
4
+ data.tar.gz: 9af04bff3851e7806fc637c2fc0ad583decf8e7c020edf841220a2b90fe8aaf4
5
5
  SHA512:
6
- metadata.gz: 6b1a96900494faa73b223e5905897299799c9a5432aa6cbdd95c6881c48c8666fe82f1653907231918fd027413eed3036da64a21087bfded6307a434c3de63b2
7
- data.tar.gz: 6456acef3636bf7f857611a22407c4f4a053c632a973b2dfc05913d99b55a338c3a4dbce41d03074c7589354cc4bad2765c5e4be683d2a080248ea30b6a22b04
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
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  class RBTree
4
4
  # The version of the rbtree-ruby gem
5
- VERSION = "0.3.4"
5
+ VERSION = "0.3.5"
6
6
  end
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 = rightmost(@root)) != @nil_node
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
- if reverse
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
- if reverse
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
- if reverse
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
- if reverse
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
- if reverse
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
- if reverse
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 = ((n = @min_node) == @nil_node) ? nil : n
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 nil unless overwrite
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 nil unless overwrite
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
- z = allocate_node(key, value, Node::RED, @nil_node, @nil_node, @nil_node)
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 # Add to hash index
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(node, min, max, include_min, include_max, safe: false, &block)
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
- if !max
726
- while pair
727
- current_key = pair[0]
728
- yield pair
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
- current = node
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(node, min, max, include_min, include_max, safe: false, &block)
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
- if !min
783
- while pair
784
- current_key = pair[0]
785
- yield pair
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
- current = node
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
- # Traverses the tree in ascending order (in-order traversal).
826
- #
827
- # @param node [Node] the current node
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 current node
844
- # @param key [Object] the upper bound (exclusive)
845
- # @yield [key, value] each matching key-value pair
846
- # @return [void]
847
- def traverse_lt_asc(node, key, safe: false, &block) =
848
- traverse_range_asc(node, nil, key, false, false, safe: safe, &block)
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
- # Traverses nodes with keys less than or equal to the specified key.
814
+ # Returns the successor of the given node.
851
815
  #
852
- # @param node [Node] the current node
853
- # @param key [Object] the upper bound (inclusive)
854
- # @yield [key, value] each matching key-value pair
855
- # @return [void]
856
- def traverse_lte_asc(node, key, safe: false, &block) =
857
- traverse_range_asc(node, nil, key, false, true, safe: safe, &block)
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
- # Traverses nodes with keys greater than the specified key.
830
+ # Resolves the startup node for ascending traversal.
860
831
  #
861
- # @param node [Node] the current node
862
- # @param key [Object] the lower bound (exclusive)
863
- # @yield [key, value] each matching key-value pair
864
- # @return [void]
865
- def traverse_gt_asc(node, key, safe: false, &block) =
866
- traverse_range_asc(node, key, nil, false, false, safe: safe, &block)
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
- # Traverses nodes with keys greater than or equal to the specified key.
869
- #
870
- # @param node [Node] the current node
871
- # @param key [Object] the lower bound (inclusive)
872
- # @yield [key, value] each matching key-value pair
873
- # @return [void]
874
- def traverse_gte_asc(node, key, safe: false, &block) =
875
- traverse_range_asc(node, key, nil, true, false, safe: safe, &block)
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
- # Traverses nodes with keys within the specified range.
878
- #
879
- # @param node [Node] the current node
880
- # @param min [Object] the lower bound
881
- # @param max [Object] the upper bound
882
- # @param include_min [Boolean] whether to include the lower bound
883
- # @param include_max [Boolean] whether to include the upper bound
884
- # @yield [key, value] each matching key-value pair
885
- # @return [void]
886
- def traverse_between_asc(node, min, max, include_min, include_max, safe: false, &block) =
887
- traverse_range_asc(node, min, max, include_min, include_max, safe: safe, &block)
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 nodes with keys less than the specified key in descending order.
877
+ # Traverses the tree in ascending order.
890
878
  #
891
- # @param node [Node] the current node
892
- # @param key [Object] the upper bound (exclusive)
893
- # @yield [key, value] each matching key-value pair in descending order
894
- # @return [void]
895
- def traverse_lt_desc(node, key, safe: false, &block) =
896
- traverse_range_desc(node, nil, key, false, false, safe: safe, &block)
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
- # Traverses nodes with keys less than or equal to the specified key in descending order.
908
+ # Resolves the startup node for descending traversal.
899
909
  #
900
- # @param node [Node] the current node
901
- # @param key [Object] the upper bound (inclusive)
902
- # @yield [key, value] each matching key-value pair in descending order
903
- # @return [void]
904
- def traverse_lte_desc(node, key, safe: false, &block) =
905
- traverse_range_desc(node, nil, key, false, true, safe: safe, &block)
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
- # Traverses nodes with keys greater than the specified key in descending order.
908
- #
909
- # @param node [Node] the current node
910
- # @param key [Object] the lower bound (exclusive)
911
- # @yield [key, value] each matching key-value pair in descending order
912
- # @return [void]
913
- def traverse_gt_desc(node, key, safe: false, &block) =
914
- traverse_range_desc(node, key, nil, false, false, safe: safe, &block)
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
- # Traverses nodes with keys greater than or equal to the specified key in descending order.
917
- #
918
- # @param node [Node] the current node
919
- # @param key [Object] the lower bound (inclusive)
920
- # @yield [key, value] each matching key-value pair in descending order
921
- # @return [void]
922
- def traverse_gte_desc(node, key, safe: false, &block) =
923
- traverse_range_desc(node, key, nil, true, false, safe: safe, &block)
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 nodes with keys within the specified range in descending order.
955
+ # Traverses the tree in descending order.
926
956
  #
927
- # @param node [Node] the current node
928
- # @param min [Object] the lower bound
929
- # @param max [Object] the upper bound
930
- # @param include_min [Boolean] whether to include the lower bound
931
- # @param include_max [Boolean] whether to include the upper bound
932
- # @yield [key, value] each matching key-value pair in descending order
933
- # @return [void]
934
- def traverse_between_desc(node, min, max, include_min, include_max, safe: false, &block) =
935
- traverse_range_desc(node, min, max, include_min, include_max, safe: safe, &block)
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
- # Check if key exists using O(1) hash lookup
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
- # Key exists: find predecessor in subtree or ancestors
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 < min_key
1223
- # Check if key exists using O(1) hash lookup
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
- # Key exists: find successor in subtree or ancestors
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 = rightmost(@root)) != @nil_node) && n.pair
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
- if (node = @hash_index[key])
1700
- node.value << value
1738
+ insert_entry_generic(key) do |node, is_new|
1701
1739
  @value_count += 1
1702
- return true
1703
- end
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
- x = x.right
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.
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.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahito Suzuki