rbtree-ruby 0.3.3 → 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: 2145d91a96cc64b7e65c65460716b678301f74d6decdffa2dbd077a914273c21
4
- data.tar.gz: 66503c542ce07672a493830afbfcb66ebfea14d8563e67b1b2dd1d5ecd1d9c48
3
+ metadata.gz: d89cd253c189b3342f30e2a4df3a293561e2353af6592d86f25fcea5eeb76433
4
+ data.tar.gz: 9af04bff3851e7806fc637c2fc0ad583decf8e7c020edf841220a2b90fe8aaf4
5
5
  SHA512:
6
- metadata.gz: 7befca8277d78b75242a443c2ec155bded73f899222b32330c6416e6ffeaad8ec6bca01e11ce23c6047208871cf71cfb8c6a742732b4ae0b531f54e51a8e424e
7
- data.tar.gz: eb77127897b535277717575afe27c47cd087ba15685f59b0df67516c55d393a8d7412cf08d667cea5494978746967c098294bb73e8e9771c2aee363762afcca9
6
+ metadata.gz: a9708ffb8b421f883953c68582c607b9333e671542e21c0f3e0927838d2bb68202142150c11dd11541ea79fa1fcafc6c3195456c38d4b0db6b3897272d205835
7
+ data.tar.gz: 601190e616b7b478cc6ceabe682e9d43ef169871753667fe61e326f65daceaa60441122a550f8458f1d4a7a794a032ac0add88f326956db903854ee96e34b613
data/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ 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
+
19
+ ## [0.3.4] - 2026-01-25
20
+
21
+ ### Added
22
+ - **Range-based Lookup**: Support for passing `Range` objects to the bracket operator `[]`.
23
+ - `tree[2..4]` -> `between(2, 4)`
24
+ - `tree[2...4]` -> `between(2, 4, include_max: false)`
25
+ - `tree[5..]` -> `gte(5)`
26
+ - `tree[..10]` -> `lte(10)`
27
+ - `tree[...10]` -> `lt(10)`
28
+ - Returns an `Enumerator` of `[key, value]` pairs.
29
+ - *Note: `gt` (greater than exclusive) is not covered by standard Ruby Range syntax and remains available via the `gt` method.*
30
+
8
31
  ## [0.3.3] - 2026-01-23
9
32
 
10
33
  ### Optimized
@@ -207,6 +230,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
207
230
  - ASCII diagrams for tree rotation operations
208
231
  - MIT License (Copyright © 2026 Masahito Suzuki)
209
232
 
233
+ [0.3.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.5
234
+ [0.3.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.4
235
+ [0.3.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.3
210
236
  [0.3.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.2
211
237
  [0.3.1]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.1
212
238
  [0.3.0]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.0
data/README.ja.md CHANGED
@@ -80,9 +80,16 @@ tree.max # => [20, "twenty"]
80
80
 
81
81
  # 範囲クエリ(Enumeratorを返す、配列には.to_aを使用)
82
82
  tree.lt(10).to_a # => [[1, "one"], [2, "two"], [3, "three"]]
83
- tree.gte(10).to_a # => [[10, "ten"], [20, "twenty"]]
83
+ tree.gte(10).each { |k, v| puts k } # ブロック渡しでのループ
84
84
  tree.between(2, 10).to_a # => [[2, "two"], [3, "three"], [10, "ten"]]
85
85
 
86
+ # []でのRangeオブジェクトの利用 (v0.3.4+)
87
+ tree[..10].to_a # lte(10)相当
88
+ tree[2..10].each { |k, v| ... } # Rangeでのループ
89
+ tree[2...10].to_a # between(2, 10, include_max: false)相当
90
+ tree[10..].to_a # gte(10)相当
91
+ tree[2..10, reverse: true].to_a # オプション指定可能
92
+
86
93
  # shiftとpop
87
94
  tree.shift # => [1, "one"] (最小値を削除)
88
95
  tree.pop # => [20, "twenty"] (最大値を削除)
@@ -232,10 +239,9 @@ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
232
239
  - `delete(key)` - O(log n)
233
240
  - `value(key)` / `[]` - **O(1)** (内部ハッシュインデックスによる超高速アクセス)
234
241
  - `has_key?` - **O(1)** (内部ハッシュインデックスによる超高速チェック)
235
- - `min` - **O(1)**
236
- - `max` - O(log n)
242
+ - `min` / `max` - **O(1)**
237
243
  - `shift` / `pop` - O(log n)
238
- - `prev` / `succ` - O(log n)、O(1)ハッシュチェック付き
244
+ - `prev` / `succ` - O(log n)、O(1)ハッシュチェックと高速な走査開始により改善
239
245
 
240
246
  全要素のイテレーションはO(n)時間。
241
247
 
data/README.md CHANGED
@@ -80,9 +80,16 @@ tree.max # => [20, "twenty"]
80
80
 
81
81
  # Range queries (return Enumerator, use .to_a for Array)
82
82
  tree.lt(10).to_a # => [[1, "one"], [2, "two"], [3, "three"]]
83
- tree.gte(10).to_a # => [[10, "ten"], [20, "twenty"]]
83
+ tree.gte(10).each { |k, v| puts k } # Block iteration
84
84
  tree.between(2, 10).to_a # => [[2, "two"], [3, "three"], [10, "ten"]]
85
85
 
86
+ # Range objects in [] (v0.3.4+)
87
+ tree[..10].to_a # lte(10)
88
+ tree[2..10].each { |k, v| ... } # Block iteration on Range
89
+ tree[2...10].to_a # between(2, 10, include_max: false)
90
+ tree[10..].to_a # gte(10)
91
+ tree[2..10, reverse: true].to_a # with options
92
+
86
93
  # Shift and pop
87
94
  tree.shift # => [1, "one"] (removes minimum)
88
95
  tree.pop # => [20, "twenty"] (removes maximum)
@@ -232,10 +239,9 @@ All major operations run in **O(log n)** time:
232
239
  - `delete(key)` - O(log n)
233
240
  - `value(key)` / `[]` - **O(1)** (hybrid hash index)
234
241
  - `has_key?` - **O(1)** (hybrid hash index)
235
- - `min` - **O(1)**
236
- - `max` - O(log n)
242
+ - `min` / `max` - **O(1)**
237
243
  - `shift` / `pop` - O(log n)
238
- - `prev` / `succ` - O(log n) with O(1) hash check
244
+ - `prev` / `succ` - O(log n) with O(1) hash check and faster startup
239
245
 
240
246
  Iteration over all elements takes O(n) time.
241
247
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  class RBTree
4
4
  # The version of the rbtree-ruby gem
5
- VERSION = "0.3.3"
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
@@ -191,11 +192,35 @@ class RBTree
191
192
  # @example
192
193
  # tree = RBTree.new({1 => 'one', 2 => 'two'})
193
194
  # tree.get(1) # => "one"
194
- # tree[2] # => "two"
195
- # tree[3] # => nil
196
195
  def value(key) = @hash_index[key]&.value
197
196
  alias :get :value
198
- alias :[] :value
197
+
198
+ # Retrieves a value associated with the given key, or a range of entries if a Range is provided.
199
+ #
200
+ # @param key_or_range [Object, Range] the key to look up or a Range for query
201
+ # @param ... [Hash] additional options to pass to the respective lookup method
202
+ # @return [Object, Enumerator, nil]
203
+ # - If a key is provided: the associated value, or nil if not found
204
+ # - If a Range is provided: an Enumerator yielding [key, value] pairs
205
+ # @example Single key lookup
206
+ # tree[2] # => "two"
207
+ # @example Range lookup
208
+ # tree[2..4].to_a # => [[2, "two"], [3, "three"], [4, "four"]]
209
+ # tree[...3].to_a # => [[1, "one"], [2, "two"]]
210
+ def [](key_or_range, **)
211
+ return value(key_or_range, **) if !key_or_range.is_a?(Range)
212
+
213
+ r = key_or_range
214
+ r.begin ? (
215
+ r.end ?
216
+ between(r.begin, r.end, include_max: !r.exclude_end?, **) :
217
+ gte(r.begin, **)
218
+ ) : (
219
+ r.end ?
220
+ (r.exclude_end? ? lt(r.end, **) : lte(r.end, **)) :
221
+ each(**)
222
+ )
223
+ end
199
224
 
200
225
  # Returns the key with the key closest to the given key.
201
226
  #
@@ -394,7 +419,7 @@ class RBTree
394
419
  # tree.pop # => [3, "three"]
395
420
  # tree.pop # => [2, "two"]
396
421
  def pop
397
- return nil unless (n = rightmost(@root)) != @nil_node
422
+ return nil unless (n = @max_node) != @nil_node
398
423
  pair = n.pair
399
424
  delete(n.key)
400
425
  pair
@@ -404,7 +429,7 @@ class RBTree
404
429
  #
405
430
  # @return [RBTree] self
406
431
  def clear
407
- @root = @min_node = @nil_node
432
+ @root = @min_node = @max_node = @nil_node
408
433
  @hash_index.clear
409
434
  @key_count = 0
410
435
  self
@@ -460,11 +485,7 @@ class RBTree
460
485
  # end
461
486
  def each(reverse: false, safe: false, &block)
462
487
  return enum_for(__method__, reverse: reverse, safe: safe) { size } unless block_given?
463
- if reverse
464
- traverse_all_desc(@root, safe: safe, &block)
465
- else
466
- traverse_all_asc(@root, safe: safe, &block)
467
- end
488
+ traverse_range(reverse, nil, nil, false, false, safe: safe, &block)
468
489
  self
469
490
  end
470
491
 
@@ -506,11 +527,7 @@ class RBTree
506
527
  # tree.lt(3, safe: true) { |k, _| tree.delete(k) if k.even? } # safe to delete
507
528
  def lt(key, reverse: false, safe: false, &block)
508
529
  return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
509
- if reverse
510
- traverse_lt_desc(@root, key, safe: safe, &block)
511
- else
512
- traverse_lt_asc(@root, key, safe: safe, &block)
513
- end
530
+ traverse_range(reverse, nil, key, false, false, safe: safe, &block)
514
531
  self
515
532
  end
516
533
 
@@ -527,11 +544,7 @@ class RBTree
527
544
  # tree.lte(3, reverse: true).first # => [3, "three"]
528
545
  def lte(key, reverse: false, safe: false, &block)
529
546
  return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
530
- if reverse
531
- traverse_lte_desc(@root, key, safe: safe, &block)
532
- else
533
- traverse_lte_asc(@root, key, safe: safe, &block)
534
- end
547
+ traverse_range(reverse, nil, key, false, true, safe: safe, &block)
535
548
  self
536
549
  end
537
550
 
@@ -548,11 +561,7 @@ class RBTree
548
561
  # tree.gt(2, reverse: true).first # => [4, "four"]
549
562
  def gt(key, reverse: false, safe: false, &block)
550
563
  return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
551
- if reverse
552
- traverse_gt_desc(@root, key, safe: safe, &block)
553
- else
554
- traverse_gt_asc(@root, key, safe: safe, &block)
555
- end
564
+ traverse_range(reverse, key, nil, false, false, safe: safe, &block)
556
565
  self
557
566
  end
558
567
 
@@ -569,11 +578,7 @@ class RBTree
569
578
  # tree.gte(2, reverse: true).first # => [4, "four"]
570
579
  def gte(key, reverse: false, safe: false, &block)
571
580
  return enum_for(__method__, key, reverse: reverse, safe: safe) unless block_given?
572
- if reverse
573
- traverse_gte_desc(@root, key, safe: safe, &block)
574
- else
575
- traverse_gte_asc(@root, key, safe: safe, &block)
576
- end
581
+ traverse_range(reverse, key, nil, true, false, safe: safe, &block)
577
582
  self
578
583
  end
579
584
 
@@ -593,11 +598,7 @@ class RBTree
593
598
  # tree.between(2, 4, reverse: true).first # => [4, "four"]
594
599
  def between(min, max, include_min: true, include_max: true, reverse: false, safe: false, &block)
595
600
  return enum_for(__method__, min, max, include_min: include_min, include_max: include_max, reverse: reverse, safe: safe) unless block_given?
596
- if reverse
597
- traverse_between_desc(@root, min, max, include_min, include_max, safe: safe, &block)
598
- else
599
- traverse_between_asc(@root, min, max, include_min, include_max, safe: safe, &block)
600
- end
601
+ traverse_range(reverse, min, max, include_min, include_max, safe: safe, &block)
601
602
  self
602
603
  end
603
604
 
@@ -631,9 +632,8 @@ class RBTree
631
632
  # @!visibility private
632
633
  private
633
634
 
634
- def min_node = ((n = @min_node) == @nil_node) ? nil : n
635
-
636
- 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
637
637
 
638
638
  # Inserts a single key-value pair.
639
639
  #
@@ -642,27 +642,51 @@ class RBTree
642
642
  # @param overwrite [Boolean] whether to overwrite existing keys (default: true)
643
643
  # @return [Boolean, nil] true if inserted/updated, nil if key exists and overwrite is false
644
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)
645
669
  if (node = @hash_index[key])
646
- return nil unless overwrite
647
- node.value = value
648
- return true
670
+ return yield(node, false)
649
671
  end
672
+
650
673
  y = @nil_node
651
674
  x = @root
652
675
  while x != @nil_node
653
676
  y = x
654
677
  cmp = key <=> x.key
655
678
  if cmp == 0
656
- return nil unless overwrite
657
- x.value = value
658
- return true
679
+ return yield(x, false)
659
680
  elsif cmp < 0
660
681
  x = x.left
661
682
  else
662
683
  x = x.right
663
684
  end
664
685
  end
665
- 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)
666
690
  z.parent = y
667
691
  if y == @nil_node
668
692
  @root = z
@@ -679,14 +703,34 @@ class RBTree
679
703
  if @min_node == @nil_node || (key <=> @min_node.key) < 0
680
704
  @min_node = z
681
705
  end
706
+ if @max_node == @nil_node || (key <=> @max_node.key) > 0
707
+ @max_node = z
708
+ end
682
709
 
683
- @hash_index[key] = z # Add to hash index
710
+ @hash_index[key] = z
684
711
  true
685
712
  end
686
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
+
687
732
  # Traverses the tree in ascending order (in-order traversal).
688
733
  #
689
- # @param node [Node] the current node
690
734
  # @param min [Object] the lower bound (inclusive)
691
735
  # @param max [Object] the upper bound (inclusive)
692
736
  # @param include_min [Boolean] whether to include the lower bound
@@ -694,56 +738,31 @@ class RBTree
694
738
  # @param safe [Boolean] whether to use safe traversal
695
739
  # @yield [key, value] each key-value pair in ascending order
696
740
  # @return [void]
697
- 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
+
698
749
  if safe
699
750
  pair = !min ? find_min :
700
751
  include_min && @hash_index[min]&.pair || find_successor(min)
701
- if !max
702
- while pair
703
- current_key = pair[0]
704
- yield pair
705
- pair = find_successor(current_key)
706
- end
707
- else
708
- while pair && pair[0] < max
709
- current_key = pair[0]
710
- yield pair
711
- pair = find_successor(current_key)
712
- end
752
+ while pair && (!max || pair[0] < max)
753
+ current_key = pair[0]
754
+ yield pair
755
+ pair = find_successor(current_key)
713
756
  end
714
757
  yield pair if pair && max && include_max && pair[0] == max
715
758
  else
716
- stack = []
717
- current = node
718
- while current != @nil_node || !stack.empty?
719
- while current != @nil_node
720
- if min && ((current.key <=> min) < 0 ||
721
- (!include_min && (current.key <=> min) == 0))
722
- current = current.right
723
- else
724
- stack << current
725
- current = current.left
726
- end
727
- end
728
-
729
- if !stack.empty?
730
- current = stack.pop
731
-
732
- if max && ((current.key <=> max) > 0 ||
733
- (!include_max && (current.key <=> max) == 0))
734
- return
735
- else
736
- yield current.pair
737
- current = current.right
738
- end
739
- end
740
- end
759
+ start_node, stack = resolve_startup_asc(min, include_min)
760
+ traverse_from_asc(start_node, stack, max, include_max, &block)
741
761
  end
742
762
  end
743
763
 
744
764
  # Traverses the tree in descending order (reverse in-order traversal).
745
765
  #
746
- # @param node [Node] the current node
747
766
  # @param min [Object] the lower bound (inclusive)
748
767
  # @param max [Object] the upper bound (inclusive)
749
768
  # @param include_min [Boolean] whether to include the lower bound
@@ -751,164 +770,218 @@ class RBTree
751
770
  # @param safe [Boolean] whether to use safe traversal
752
771
  # @yield [key, value] each key-value pair in descending order
753
772
  # @return [void]
754
- 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
+
755
781
  if safe
756
782
  pair = !max ? find_max :
757
783
  include_max && @hash_index[max]&.pair || find_predecessor(max)
758
- if !min
759
- while pair
760
- current_key = pair[0]
761
- yield pair
762
- pair = find_predecessor(current_key)
763
- end
764
- else
765
- while pair && pair[0] > min
766
- current_key = pair[0]
767
- yield pair
768
- pair = find_predecessor(current_key)
769
- end
784
+ while pair && (!min || pair[0] > min)
785
+ current_key = pair[0]
786
+ yield pair
787
+ pair = find_predecessor(current_key)
770
788
  end
771
789
  yield pair if pair && min && include_min && pair[0] == min
772
790
  else
773
- stack = []
774
- current = node
775
- while current != @nil_node || !stack.empty?
776
- while current != @nil_node
777
- if max && ((current.key <=> max) > 0 ||
778
- (!include_max && (current.key <=> max) == 0))
779
- current = current.left
780
- else
781
- stack << current
782
- current = current.right
783
- end
784
- end
785
-
786
- if !stack.empty?
787
- current = stack.pop
788
-
789
- if min && ((current.key <=> min) < 0 ||
790
- (!include_min && (current.key <=> min) == 0))
791
- return
792
- else
793
- yield current.pair
794
- current = current.left
795
- end
796
- end
797
- end
791
+ start_node, stack = resolve_startup_desc(max, include_max)
792
+ traverse_from_desc(start_node, stack, min, include_min, &block)
798
793
  end
799
794
  end
800
795
 
801
- # Traverses the tree in ascending order (in-order traversal).
802
- #
803
- # @param node [Node] the current node
804
- # @yield [key, value] each key-value pair in ascending order
805
- # @return [void]
806
- def traverse_all_asc(node, safe: false, &block) =
807
- traverse_range_asc(node, nil, nil, false, false, safe: safe, &block)
808
-
809
- # Traverses the tree in descending order (reverse in-order traversal).
810
- #
811
- # @param node [Node] the current node
812
- # @yield [key, value] each key-value pair in descending order
813
- # @return [void]
814
- def traverse_all_desc(node, safe: false, &block) =
815
- traverse_range_desc(node, nil, nil, false, false, safe: safe, &block)
816
-
817
- # Traverses nodes with keys less than the specified key.
796
+ private
797
+
798
+ # Returns the predecessor of the given node.
818
799
  #
819
- # @param node [Node] the current node
820
- # @param key [Object] the upper bound (exclusive)
821
- # @yield [key, value] each matching key-value pair
822
- # @return [void]
823
- def traverse_lt_asc(node, key, safe: false, &block) =
824
- 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
825
813
 
826
- # Traverses nodes with keys less than or equal to the specified key.
814
+ # Returns the successor of the given node.
827
815
  #
828
- # @param node [Node] the current node
829
- # @param key [Object] the upper bound (inclusive)
830
- # @yield [key, value] each matching key-value pair
831
- # @return [void]
832
- def traverse_lte_asc(node, key, safe: false, &block) =
833
- 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
834
829
 
835
- # Traverses nodes with keys greater than the specified key.
830
+ # Resolves the startup node for ascending traversal.
836
831
  #
837
- # @param node [Node] the current node
838
- # @param key [Object] the lower bound (exclusive)
839
- # @yield [key, value] each matching key-value pair
840
- # @return [void]
841
- def traverse_gt_asc(node, key, safe: false, &block) =
842
- 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
843
846
 
844
- # Traverses nodes with keys greater than or equal to the specified key.
845
- #
846
- # @param node [Node] the current node
847
- # @param key [Object] the lower bound (inclusive)
848
- # @yield [key, value] each matching key-value pair
849
- # @return [void]
850
- def traverse_gte_asc(node, key, safe: false, &block) =
851
- 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
852
860
 
853
- # Traverses nodes with keys within the specified range.
854
- #
855
- # @param node [Node] the current node
856
- # @param min [Object] the lower bound
857
- # @param max [Object] the upper bound
858
- # @param include_min [Boolean] whether to include the lower bound
859
- # @param include_max [Boolean] whether to include the upper bound
860
- # @yield [key, value] each matching key-value pair
861
- # @return [void]
862
- def traverse_between_asc(node, min, max, include_min, include_max, safe: false, &block) =
863
- 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
864
876
 
865
- # Traverses nodes with keys less than the specified key in descending order.
877
+ # Traverses the tree in ascending order.
866
878
  #
867
- # @param node [Node] the current node
868
- # @param key [Object] the upper bound (exclusive)
869
- # @yield [key, value] each matching key-value pair in descending order
870
- # @return [void]
871
- def traverse_lt_desc(node, key, safe: false, &block) =
872
- 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
873
907
 
874
- # Traverses nodes with keys less than or equal to the specified key in descending order.
908
+ # Resolves the startup node for descending traversal.
875
909
  #
876
- # @param node [Node] the current node
877
- # @param key [Object] the upper bound (inclusive)
878
- # @yield [key, value] each matching key-value pair in descending order
879
- # @return [void]
880
- def traverse_lte_desc(node, key, safe: false, &block) =
881
- 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
882
924
 
883
- # Traverses nodes with keys greater than the specified key in descending order.
884
- #
885
- # @param node [Node] the current node
886
- # @param key [Object] the lower bound (exclusive)
887
- # @yield [key, value] each matching key-value pair in descending order
888
- # @return [void]
889
- def traverse_gt_desc(node, key, safe: false, &block) =
890
- 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
891
938
 
892
- # Traverses nodes with keys greater than or equal to the specified key in descending order.
893
- #
894
- # @param node [Node] the current node
895
- # @param key [Object] the lower bound (inclusive)
896
- # @yield [key, value] each matching key-value pair in descending order
897
- # @return [void]
898
- def traverse_gte_desc(node, key, safe: false, &block) =
899
- 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
900
954
 
901
- # Traverses nodes with keys within the specified range in descending order.
955
+ # Traverses the tree in descending order.
902
956
  #
903
- # @param node [Node] the current node
904
- # @param min [Object] the lower bound
905
- # @param max [Object] the upper bound
906
- # @param include_min [Boolean] whether to include the lower bound
907
- # @param include_max [Boolean] whether to include the upper bound
908
- # @yield [key, value] each matching key-value pair in descending order
909
- # @return [void]
910
- def traverse_between_desc(node, min, max, include_min, include_max, safe: false, &block) =
911
- 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
912
985
 
913
986
  # Restores red-black tree properties after insertion.
914
987
  #
@@ -1012,6 +1085,10 @@ class RBTree
1012
1085
  if next_min_node
1013
1086
  @min_node = next_min_node
1014
1087
  end
1088
+ if z == @max_node
1089
+ next_max_node = predecessor_node_of(z)
1090
+ @max_node = next_max_node
1091
+ end
1015
1092
 
1016
1093
  value = z.value
1017
1094
  release_node(z)
@@ -1104,6 +1181,13 @@ class RBTree
1104
1181
  # @param key [Numeric] the target key
1105
1182
  # @return [Node] the nearest node, or @nil_node if tree is empty
1106
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
+
1107
1191
  current = @root
1108
1192
  closest = @nil_node
1109
1193
  min_dist = nil
@@ -1144,21 +1228,11 @@ class RBTree
1144
1228
  # @param key [Object] the reference key
1145
1229
  # @return [Node] the predecessor node, or @nil_node if none exists
1146
1230
  def find_predecessor_node(key)
1147
- # 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
1148
1234
  if (node = @hash_index[key])
1149
- # Key exists: find predecessor in subtree or ancestors
1150
- if node.left != @nil_node
1151
- return rightmost(node.left)
1152
- else
1153
- # Walk up to find first ancestor where we came from the right
1154
- current = node
1155
- parent = current.parent
1156
- while parent != @nil_node && current == parent.left
1157
- current = parent
1158
- parent = parent.parent
1159
- end
1160
- return parent
1161
- end
1235
+ return predecessor_node_of(node)
1162
1236
  end
1163
1237
 
1164
1238
  # Key doesn't exist: descend tree tracking the best candidate
@@ -1194,25 +1268,14 @@ class RBTree
1194
1268
  # @param key [Object] the reference key
1195
1269
  # @return [Node] the successor node, or @nil_node if none exists
1196
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
1197
1273
  # If key is smaller than min_key, return min_node
1198
- return @min_node if min_key && key < min_key
1199
- # 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
1200
1276
  if (node = @hash_index[key])
1201
- # Key exists: find successor in subtree or ancestors
1202
- if node.right != @nil_node
1203
- return leftmost(node.right)
1204
- else
1205
- # Walk up to find first ancestor where we came from the left
1206
- current = node
1207
- parent = current.parent
1208
- while parent != @nil_node && current == parent.right
1209
- current = parent
1210
- parent = parent.parent
1211
- end
1212
- return parent
1213
- end
1277
+ return successor_node_of(node)
1214
1278
  end
1215
-
1216
1279
  # Key doesn't exist: descend tree tracking the best candidate
1217
1280
  current = @root
1218
1281
  successor = @nil_node
@@ -1268,7 +1331,7 @@ class RBTree
1268
1331
  # Returns the maximum key-value pair.
1269
1332
  #
1270
1333
  # @return [Array(Object, Object), nil] a two-element array [key, value], or nil if tree is empty
1271
- def find_max = ((n = rightmost(@root)) != @nil_node) && n.pair
1334
+ def find_max = ((n = @max_node) != @nil_node) && n.pair
1272
1335
 
1273
1336
  # Performs a left rotation on the given node.
1274
1337
  #
@@ -1499,7 +1562,6 @@ class MultiRBTree < RBTree
1499
1562
  # tree.get(1, last: true) # => "second"
1500
1563
  def value(key, last: false) = @hash_index[key]&.value&.send(last ? :last : :first)
1501
1564
  alias :get :value
1502
- alias :[] :value
1503
1565
 
1504
1566
  # Retrieves the first value associated with the given key.
1505
1567
  #
@@ -1673,47 +1735,15 @@ class MultiRBTree < RBTree
1673
1735
  # tree.insert(1, 'first')
1674
1736
  # tree.insert(1, 'second') # adds another value for key 1
1675
1737
  def insert_entry(key, value, **)
1676
- if (node = @hash_index[key])
1677
- node.value << value
1738
+ insert_entry_generic(key) do |node, is_new|
1678
1739
  @value_count += 1
1679
- return true
1680
- end
1681
- y = @nil_node
1682
- x = @root
1683
- while x != @nil_node
1684
- y = x
1685
- cmp = key <=> x.key
1686
- if cmp == 0
1687
- x.value << value
1688
- @value_count += 1
1689
- return true
1690
- elsif cmp < 0
1691
- x = x.left
1740
+ if is_new
1741
+ [value]
1692
1742
  else
1693
- x = x.right
1743
+ node.value << value
1744
+ true
1694
1745
  end
1695
1746
  end
1696
- z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
1697
- z.parent = y
1698
- if y == @nil_node
1699
- @root = z
1700
- elsif (key <=> y.key) < 0
1701
- y.left = z
1702
- else
1703
- y.right = z
1704
- end
1705
- z.left = @nil_node
1706
- z.right = @nil_node
1707
- z.color = Node::RED
1708
- insert_fixup(z)
1709
- @value_count += 1
1710
-
1711
- if @min_node == @nil_node || (key <=> @min_node.key) < 0
1712
- @min_node = z
1713
- end
1714
-
1715
- @hash_index[key] = z # Add to hash index
1716
- true
1717
1747
  end
1718
1748
 
1719
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.3
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahito Suzuki