rbtree-ruby 0.3.4 → 0.3.6

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: 18d0cdfd5f115b61096c266d9de0a482e263574aad1dfce425efaabd3dad67b7
4
+ data.tar.gz: 639a4f4f85c14cab4acf93f5b9b5280fb6225c96c575cac1a47b2b4f934dca8e
5
5
  SHA512:
6
- metadata.gz: 6b1a96900494faa73b223e5905897299799c9a5432aa6cbdd95c6881c48c8666fe82f1653907231918fd027413eed3036da64a21087bfded6307a434c3de63b2
7
- data.tar.gz: 6456acef3636bf7f857611a22407c4f4a053c632a973b2dfc05913d99b55a338c3a4dbce41d03074c7589354cc4bad2765c5e4be683d2a080248ea30b6a22b04
6
+ metadata.gz: e40c1a174c7f2dd1c6df7f94e316070178457e0baa41322baa0f622157eb548245bfd334435488d3a49cfe8396e8d587ae80f886a17dadbf93cd9f03fcd701d7
7
+ data.tar.gz: 20f49f91135a98e4b46ccb4b0981dafab6bb35a9375bfb06024b7b42f5c71ad04b23613ad5a1f372ac6456d9edf03ef9698b68ca2228e0cf5cb85d6c2ae2b5f4
data/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.6] - 2026-01-28
9
+
10
+ ### Added
11
+
12
+ > **Note:** All methods added in this release are **convenience methods** composed from existing public API primitives (`each`, `insert`, `delete`, `dup`, `merge!`, `safe: true`). They provide no speed advantage over manual composition — their value is purely in readability and API completeness.
13
+
14
+ - **`dup` (Deep Copy)**: `RBTree` and `MultiRBTree` now support `dup` and `clone` via `initialize_copy`. Creates an independent deep copy of the tree — modifications to the copy do not affect the original and vice versa.
15
+ - **`select`**: Returns a new tree containing only key-value pairs for which the block returns true. Returns an `Enumerator` if no block is given.
16
+ - **`reject`**: Returns a new tree excluding key-value pairs for which the block returns true. Returns an `Enumerator` if no block is given.
17
+ - **`delete_if`**: Deletes key-value pairs in place for which the block returns true. Returns self. In `MultiRBTree`, operates at individual value granularity (can remove specific values without deleting the entire key).
18
+ - **`reject!`**: Same as `delete_if`, but returns `nil` if no changes were made.
19
+ - **`keep_if`**: Keeps only key-value pairs for which the block returns true, deleting the rest in place. Returns self. In `MultiRBTree`, operates at individual value granularity.
20
+ - **`invert`**: Returns a new tree with keys and values swapped. For `RBTree`, duplicate values result in the last key winning (same as `Hash#invert`). For `MultiRBTree`, all pairs are preserved.
21
+ - **`merge`** (non-destructive): Returns a new tree with merged contents. Supports block for duplicate key resolution: `tree.merge(other) { |key, v1, v2| v1 }`.
22
+ - **`merge!` block support**: `merge!` now accepts a block for duplicate key resolution, matching `Hash#merge!` behavior.
23
+
24
+ ### Optimized
25
+ - **`MultiRBTree#delete_if` / `#keep_if`**: Replaced clear-and-rebuild approach with in-place value array filtering via internal `filter_values!`. Avoids O(n log n) tree reconstruction; now runs in O(n) by directly mutating each node's value array and removing only emptied nodes.
26
+
27
+ ### Fixed
28
+ - **`MultiRBTree#clear`**: Fixed `@value_count` not being reset when `clear` was called, which caused `size` to return incorrect values after clearing and re-inserting.
29
+
30
+ ## [0.3.5] - 2026-01-26
31
+
32
+ ### Optimized
33
+ - **O(1) Max Access**: Added cached `@max_node` to ensure `max` and `pop` operations run in constant time.
34
+ - **Traversal Engine Rewrite**: Optimized all range queries (`lt`, `gt`, `between`, `each`) with a unified traversal engine featuring $O(1)$ range rejection and bound optimization.
35
+ - **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.
36
+
37
+ ### Changed
38
+ - **Defensive Assertion**: Added an explicit check to `find_nearest_node` to ensure only `Numeric` keys are accepted, formalizing a pre-existing design constraint.
39
+ - **Internal Refactoring**: Unified the insertion logic between `RBTree` and `MultiRBTree` into a single internal method to improve maintainability and ensure consistent optimization.
40
+
8
41
  ## [0.3.4] - 2026-01-25
9
42
 
10
43
  ### Added
@@ -219,6 +252,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
219
252
  - ASCII diagrams for tree rotation operations
220
253
  - MIT License (Copyright © 2026 Masahito Suzuki)
221
254
 
255
+ [0.4.0]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.4.0
256
+ [0.3.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.5
222
257
  [0.3.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.4
223
258
  [0.3.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.3
224
259
  [0.3.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.2
data/README.ja.md CHANGED
@@ -197,10 +197,58 @@ tree.to_a # => [[1, "one"], [2, "two"]]
197
197
  # ハッシュへの変換
198
198
  tree.to_h # => {1 => "one", 2 => "two"}
199
199
 
200
- # 他のツリー、ハッシュ、またはEnumerableの結合
200
+ # 結合(破壊的)
201
201
  other = {3 => 'three'}
202
202
  tree.merge!(other)
203
203
  tree.size # => 3
204
+
205
+ # 結合(非破壊) — 新しいツリーを返す
206
+ merged = tree.merge({4 => 'four'})
207
+
208
+ # ブロックで重複キーの解決
209
+ merged = tree.merge({1 => 'ONE'}) { |key, old_val, new_val| old_val }
210
+
211
+ # キーと値を入れ替え
212
+ tree = RBTree.new({1 => 'a', 2 => 'b', 3 => 'c'})
213
+ tree.invert.to_a # => [["a", 1], ["b", 2], ["c", 3]]
214
+ ```
215
+
216
+ ### フィルタリングとコピー
217
+
218
+ > **注記:** `dup`、`select`、`reject`、`delete_if`、`reject!`、`keep_if`、`invert`、`merge` は既存のプリミティブを組み合わせた利便性メソッドです。手動で同等の処理を書いた場合と比べて速度上の利点はありません。可読性とAPI互換性のために提供されています。
219
+
220
+ Rubyの慣習的なメソッドでツリーのコピーやフィルタリングが可能です:
221
+
222
+ ```ruby
223
+ tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
224
+
225
+ # ディープコピー — 元のツリーと独立
226
+ copy = tree.dup
227
+ copy.delete(1)
228
+ tree.size # => 4 (変更なし)
229
+
230
+ # select / reject — 新しいツリーを返す
231
+ evens = tree.select { |k, _| k.even? } # => {2=>"two", 4=>"four"}
232
+ odds = tree.reject { |k, _| k.even? } # => {1=>"one", 3=>"three"}
233
+
234
+ # delete_if / keep_if — 破壊的に変更
235
+ tree.delete_if { |k, _| k > 2 }
236
+ tree.to_a # => [[1, "one"], [2, "two"]]
237
+
238
+ # reject! — delete_ifと同様だが、変更がなければnilを返す
239
+ tree.reject! { |_, _| false } # => nil
240
+ ```
241
+
242
+ `MultiRBTree`では、`delete_if`と`keep_if`は個々の値単位で操作します:
243
+
244
+ ```ruby
245
+ tree = MultiRBTree.new
246
+ tree.insert(1, 'a')
247
+ tree.insert(1, 'b')
248
+ tree.insert(2, 'c')
249
+
250
+ tree.delete_if { |k, v| k == 1 && v == 'a' }
251
+ tree.to_a # => [[1, "b"], [2, "c"]] — 'a'のみ削除された
204
252
  ```
205
253
 
206
254
  ### MultiRBTree 値配列アクセス
@@ -239,10 +287,9 @@ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
239
287
  - `delete(key)` - O(log n)
240
288
  - `value(key)` / `[]` - **O(1)** (内部ハッシュインデックスによる超高速アクセス)
241
289
  - `has_key?` - **O(1)** (内部ハッシュインデックスによる超高速チェック)
242
- - `min` - **O(1)**
243
- - `max` - O(log n)
290
+ - `min` / `max` - **O(1)**
244
291
  - `shift` / `pop` - O(log n)
245
- - `prev` / `succ` - O(log n)、O(1)ハッシュチェック付き
292
+ - `prev` / `succ` - O(log n)、O(1)ハッシュチェックと高速な走査開始により改善
246
293
 
247
294
  全要素のイテレーションはO(n)時間。
248
295
 
data/README.md CHANGED
@@ -197,10 +197,58 @@ tree.to_a # => [[1, "one"], [2, "two"]]
197
197
  # Convert to Hash
198
198
  tree.to_h # => {1 => "one", 2 => "two"}
199
199
 
200
- # Merge another tree, hash, or enumerable
200
+ # Merge (destructive)
201
201
  other = {3 => 'three'}
202
202
  tree.merge!(other)
203
203
  tree.size # => 3
204
+
205
+ # Merge (non-destructive) — returns a new tree
206
+ merged = tree.merge({4 => 'four'})
207
+
208
+ # Merge with block for duplicate key resolution
209
+ merged = tree.merge({1 => 'ONE'}) { |key, old_val, new_val| old_val }
210
+
211
+ # Invert keys and values
212
+ tree = RBTree.new({1 => 'a', 2 => 'b', 3 => 'c'})
213
+ tree.invert.to_a # => [["a", 1], ["b", 2], ["c", 3]]
214
+ ```
215
+
216
+ ### Filtering and Copying
217
+
218
+ > **Note:** `dup`, `select`, `reject`, `delete_if`, `reject!`, `keep_if`, `invert`, and `merge` are convenience methods composed from existing primitives. They provide no speed advantage over manual composition — their value is in readability and API completeness.
219
+
220
+ Create copies or filter trees using familiar Ruby idioms:
221
+
222
+ ```ruby
223
+ tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
224
+
225
+ # Deep copy — independent of original
226
+ copy = tree.dup
227
+ copy.delete(1)
228
+ tree.size # => 4 (unchanged)
229
+
230
+ # select / reject — return a new tree
231
+ evens = tree.select { |k, _| k.even? } # => {2=>"two", 4=>"four"}
232
+ odds = tree.reject { |k, _| k.even? } # => {1=>"one", 3=>"three"}
233
+
234
+ # delete_if / keep_if — modify in place
235
+ tree.delete_if { |k, _| k > 2 }
236
+ tree.to_a # => [[1, "one"], [2, "two"]]
237
+
238
+ # reject! — like delete_if, but returns nil if nothing changed
239
+ tree.reject! { |_, _| false } # => nil
240
+ ```
241
+
242
+ For `MultiRBTree`, `delete_if` and `keep_if` operate at individual value granularity:
243
+
244
+ ```ruby
245
+ tree = MultiRBTree.new
246
+ tree.insert(1, 'a')
247
+ tree.insert(1, 'b')
248
+ tree.insert(2, 'c')
249
+
250
+ tree.delete_if { |k, v| k == 1 && v == 'a' }
251
+ tree.to_a # => [[1, "b"], [2, "c"]] — only 'a' was removed
204
252
  ```
205
253
 
206
254
  ### MultiRBTree Value Array Access
@@ -239,10 +287,9 @@ All major operations run in **O(log n)** time:
239
287
  - `delete(key)` - O(log n)
240
288
  - `value(key)` / `[]` - **O(1)** (hybrid hash index)
241
289
  - `has_key?` - **O(1)** (hybrid hash index)
242
- - `min` - **O(1)**
243
- - `max` - O(log n)
290
+ - `min` / `max` - **O(1)**
244
291
  - `shift` / `pop` - O(log n)
245
- - `prev` / `succ` - O(log n) with O(1) hash check
292
+ - `prev` / `succ` - O(log n) with O(1) hash check and faster startup
246
293
 
247
294
  Iteration over all elements takes O(n) time.
248
295
 
@@ -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.6"
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
@@ -110,6 +111,16 @@ class RBTree
110
111
  end
111
112
  end
112
113
 
114
+ # Creates a deep copy of the tree.
115
+ # Called automatically by `dup` and `clone`.
116
+ #
117
+ # @param orig [RBTree] the original tree to copy
118
+ # @return [void]
119
+ def initialize_copy(orig)
120
+ initialize(overwrite: orig.instance_variable_get(:@overwrite))
121
+ orig.each { |k, v| insert(k, v) }
122
+ end
123
+
113
124
  # Returns a Hash containing all key-value pairs from the tree.
114
125
  #
115
126
  # @return [Hash] a new Hash with the tree's contents
@@ -368,16 +379,43 @@ class RBTree
368
379
  end
369
380
  alias :[]= :insert
370
381
 
382
+ # Returns a new tree containing the merged contents of self and other.
383
+ #
384
+ # When a block is given, it is called with (key, old_value, new_value) for
385
+ # duplicate keys, and the block's return value is used.
386
+ #
387
+ # @param other [RBTree, Hash, Enumerable] the source to merge from
388
+ # @yield [key, old_value, new_value] called for duplicate keys when block given
389
+ # @return [RBTree] a new tree with merged contents
390
+ def merge(other, &block)
391
+ dup.merge!(other, &block)
392
+ end
393
+
371
394
  # Merges the contents of another tree, hash, or enumerable into this tree.
372
395
  #
396
+ # When a block is given, it is called with (key, old_value, new_value) for
397
+ # duplicate keys, and the block's return value is used.
398
+ #
373
399
  # @param other [RBTree, Hash, Enumerable] the source to merge from
374
- # @param overwrite [Boolean] whether to overwrite existing keys (default: true)
400
+ # @param overwrite [Boolean] whether to overwrite existing keys (default: true). Ignored if block given.
401
+ # @yield [key, old_value, new_value] called for duplicate keys when block given
375
402
  # @return [RBTree] self
376
- def merge!(other, overwrite: true)
403
+ def merge!(other, overwrite: true, &block)
377
404
  if defined?(MultiRBTree) && other.is_a?(MultiRBTree)
378
405
  raise ArgumentError, "Cannot merge MultiRBTree into RBTree"
379
406
  end
380
- insert(other, overwrite: overwrite)
407
+ if block
408
+ other_enum = other.is_a?(Hash) || other.is_a?(RBTree) ? other : other.each
409
+ other_enum.each do |k, v|
410
+ if has_key?(k)
411
+ insert_entry(k, block.call(k, value(k), v), overwrite: true)
412
+ else
413
+ insert_entry(k, v)
414
+ end
415
+ end
416
+ else
417
+ insert(other, overwrite: overwrite)
418
+ end
381
419
  self
382
420
  end
383
421
 
@@ -418,7 +456,7 @@ class RBTree
418
456
  # tree.pop # => [3, "three"]
419
457
  # tree.pop # => [2, "two"]
420
458
  def pop
421
- return nil unless (n = rightmost(@root)) != @nil_node
459
+ return nil unless (n = @max_node) != @nil_node
422
460
  pair = n.pair
423
461
  delete(n.key)
424
462
  pair
@@ -428,7 +466,7 @@ class RBTree
428
466
  #
429
467
  # @return [RBTree] self
430
468
  def clear
431
- @root = @min_node = @nil_node
469
+ @root = @min_node = @max_node = @nil_node
432
470
  @hash_index.clear
433
471
  @key_count = 0
434
472
  self
@@ -484,11 +522,7 @@ class RBTree
484
522
  # end
485
523
  def each(reverse: false, safe: false, &block)
486
524
  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
525
+ traverse_range(reverse, nil, nil, false, false, safe: safe, &block)
492
526
  self
493
527
  end
494
528
 
@@ -530,11 +564,7 @@ class RBTree
530
564
  # tree.lt(3, safe: true) { |k, _| tree.delete(k) if k.even? } # safe to delete
531
565
  def lt(key, reverse: false, safe: false, &block)
532
566
  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
567
+ traverse_range(reverse, nil, key, false, false, safe: safe, &block)
538
568
  self
539
569
  end
540
570
 
@@ -551,11 +581,7 @@ class RBTree
551
581
  # tree.lte(3, reverse: true).first # => [3, "three"]
552
582
  def lte(key, reverse: false, safe: false, &block)
553
583
  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
584
+ traverse_range(reverse, nil, key, false, true, safe: safe, &block)
559
585
  self
560
586
  end
561
587
 
@@ -572,11 +598,7 @@ class RBTree
572
598
  # tree.gt(2, reverse: true).first # => [4, "four"]
573
599
  def gt(key, reverse: false, safe: false, &block)
574
600
  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
601
+ traverse_range(reverse, key, nil, false, false, safe: safe, &block)
580
602
  self
581
603
  end
582
604
 
@@ -593,11 +615,7 @@ class RBTree
593
615
  # tree.gte(2, reverse: true).first # => [4, "four"]
594
616
  def gte(key, reverse: false, safe: false, &block)
595
617
  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
618
+ traverse_range(reverse, key, nil, true, false, safe: safe, &block)
601
619
  self
602
620
  end
603
621
 
@@ -617,14 +635,76 @@ class RBTree
617
635
  # tree.between(2, 4, reverse: true).first # => [4, "four"]
618
636
  def between(min, max, include_min: true, include_max: true, reverse: false, safe: false, &block)
619
637
  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
638
+ traverse_range(reverse, min, max, include_min, include_max, safe: safe, &block)
639
+ self
640
+ end
641
+
642
+ # Returns a new tree containing key-value pairs for which the block returns true.
643
+ #
644
+ # @yield [key, value] each key-value pair
645
+ # @return [RBTree, Enumerator] a new tree with selected pairs, or Enumerator if no block
646
+ def select(&block)
647
+ return enum_for(__method__) { size } unless block_given?
648
+ result = self.class.new
649
+ each { |k, v| result.insert(k, v) if block.call(k, v) }
650
+ result
651
+ end
652
+
653
+ # Returns a new tree containing key-value pairs for which the block returns false.
654
+ #
655
+ # @yield [key, value] each key-value pair
656
+ # @return [RBTree, Enumerator] a new tree with non-rejected pairs, or Enumerator if no block
657
+ def reject(&block)
658
+ return enum_for(__method__) { size } unless block_given?
659
+ result = self.class.new
660
+ each { |k, v| result.insert(k, v) unless block.call(k, v) }
661
+ result
662
+ end
663
+
664
+ # Deletes key-value pairs for which the block returns true. Returns nil if no changes were made.
665
+ #
666
+ # @yield [key, value] each key-value pair
667
+ # @return [RBTree, nil, Enumerator] self if changed, nil if unchanged, or Enumerator if no block
668
+ def reject!(&block)
669
+ return enum_for(__method__) { size } unless block_given?
670
+ size_before = size
671
+ delete_if(&block)
672
+ size == size_before ? nil : self
673
+ end
674
+
675
+ # Keeps key-value pairs for which the block returns true, deleting the rest. Modifies the tree in place.
676
+ #
677
+ # @yield [key, value] each key-value pair
678
+ # @return [RBTree, Enumerator] self, or Enumerator if no block
679
+ def keep_if(&block)
680
+ return enum_for(__method__) { size } unless block_given?
681
+ each(safe: true) { |k, v| delete(k) unless block.call(k, v) }
625
682
  self
626
683
  end
627
684
 
685
+ # Deletes key-value pairs for which the block returns true. Modifies the tree in place.
686
+ #
687
+ # @yield [key, value] each key-value pair
688
+ # @return [RBTree, Enumerator] self, or Enumerator if no block
689
+ def delete_if(&block)
690
+ return enum_for(__method__) { size } unless block_given?
691
+ each(safe: true) { |k, v| delete(k) if block.call(k, v) }
692
+ self
693
+ end
694
+
695
+ # Returns a new tree with keys and values swapped.
696
+ #
697
+ # For RBTree, duplicate values result in later keys overwriting earlier ones.
698
+ # For MultiRBTree, all key-value pairs are preserved.
699
+ # Values must implement <=> to serve as keys in the new tree.
700
+ #
701
+ # @return [RBTree, MultiRBTree] a new tree with keys and values inverted
702
+ def invert
703
+ result = self.class.new
704
+ each { |k, v| result.insert(v, k) }
705
+ result
706
+ end
707
+
628
708
  # Returns a string representation of the tree.
629
709
  #
630
710
  # Shows the first 5 entries and total size. Useful for debugging.
@@ -655,9 +735,8 @@ class RBTree
655
735
  # @!visibility private
656
736
  private
657
737
 
658
- def min_node = ((n = @min_node) == @nil_node) ? nil : n
659
-
660
- def max_node = ((n = rightmost(@root)) == @nil_node) ? nil : n
738
+ def min_node = (@min_node == @nil_node) ? nil : @min_node
739
+ def max_node = (@max_node == @nil_node) ? nil : @max_node
661
740
 
662
741
  # Inserts a single key-value pair.
663
742
  #
@@ -666,27 +745,51 @@ class RBTree
666
745
  # @param overwrite [Boolean] whether to overwrite existing keys (default: true)
667
746
  # @return [Boolean, nil] true if inserted/updated, nil if key exists and overwrite is false
668
747
  def insert_entry(key, value, overwrite: true)
748
+ insert_entry_generic(key) do |node, is_new|
749
+ if is_new
750
+ value
751
+ else
752
+ if overwrite
753
+ node.value = value
754
+ true
755
+ else
756
+ nil
757
+ end
758
+ end
759
+ end
760
+ end
761
+
762
+ # Generic entry insertion logic shared between RBTree and MultiRBTree.
763
+ #
764
+ # @param key [Object] the key to insert
765
+ # @yield [node, is_new] yields the existing node (if any) and whether it's a new insertion
766
+ # @yieldparam node [Node, nil] the existing node or nil
767
+ # @yieldparam is_new [Boolean] true if no node with the key exists
768
+ # @yieldreturn [Object]
769
+ # - if is_new: the initial value for the new node
770
+ # - if !is_new: the value to return from insert_entry
771
+ def insert_entry_generic(key)
669
772
  if (node = @hash_index[key])
670
- return nil unless overwrite
671
- node.value = value
672
- return true
773
+ return yield(node, false)
673
774
  end
775
+
674
776
  y = @nil_node
675
777
  x = @root
676
778
  while x != @nil_node
677
779
  y = x
678
780
  cmp = key <=> x.key
679
781
  if cmp == 0
680
- return nil unless overwrite
681
- x.value = value
682
- return true
782
+ return yield(x, false)
683
783
  elsif cmp < 0
684
784
  x = x.left
685
785
  else
686
786
  x = x.right
687
787
  end
688
788
  end
689
- z = allocate_node(key, value, Node::RED, @nil_node, @nil_node, @nil_node)
789
+
790
+ initial_value = yield(nil, true)
791
+
792
+ z = allocate_node(key, initial_value, Node::RED, @nil_node, @nil_node, @nil_node)
690
793
  z.parent = y
691
794
  if y == @nil_node
692
795
  @root = z
@@ -703,14 +806,34 @@ class RBTree
703
806
  if @min_node == @nil_node || (key <=> @min_node.key) < 0
704
807
  @min_node = z
705
808
  end
809
+ if @max_node == @nil_node || (key <=> @max_node.key) > 0
810
+ @max_node = z
811
+ end
706
812
 
707
- @hash_index[key] = z # Add to hash index
813
+ @hash_index[key] = z
708
814
  true
709
815
  end
710
816
 
817
+ # Traverses the tree in a specified direction (ascending or descending).
818
+ #
819
+ # @param direction [Boolean] true for descending, false for ascending
820
+ # @param min [Object] the lower bound (inclusive)
821
+ # @param max [Object] the upper bound (inclusive)
822
+ # @param include_min [Boolean] whether to include the lower bound
823
+ # @param include_max [Boolean] whether to include the upper bound
824
+ # @param safe [Boolean] whether to use safe traversal
825
+ # @yield [key, value] each key-value pair in the specified direction
826
+ # @return [void]
827
+ def traverse_range(direction, min, max, include_min, include_max, safe: false, &block)
828
+ if direction
829
+ traverse_range_desc(min, max, include_min, include_max, safe: safe, &block)
830
+ else
831
+ traverse_range_asc(min, max, include_min, include_max, safe: safe, &block)
832
+ end
833
+ end
834
+
711
835
  # Traverses the tree in ascending order (in-order traversal).
712
836
  #
713
- # @param node [Node] the current node
714
837
  # @param min [Object] the lower bound (inclusive)
715
838
  # @param max [Object] the upper bound (inclusive)
716
839
  # @param include_min [Boolean] whether to include the lower bound
@@ -718,56 +841,31 @@ class RBTree
718
841
  # @param safe [Boolean] whether to use safe traversal
719
842
  # @yield [key, value] each key-value pair in ascending order
720
843
  # @return [void]
721
- def traverse_range_asc(node, min, max, include_min, include_max, safe: false, &block)
844
+ def traverse_range_asc(min, max, include_min, include_max, safe: false, &block)
845
+ # O(1) Range Rejection
846
+ return if min && @max_node != @nil_node && (min <=> @max_node.key) > 0
847
+ return if min && @max_node != @nil_node && !include_min && (min <=> @max_node.key) == 0
848
+
849
+ # O(1) Bound Optimization
850
+ max = nil if max && @max_node != @nil_node && (cmp = max <=> @max_node.key) >= 0 && (cmp > 0 || include_max)
851
+
722
852
  if safe
723
853
  pair = !min ? find_min :
724
854
  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
855
+ while pair && (!max || pair[0] < max)
856
+ current_key = pair[0]
857
+ yield pair
858
+ pair = find_successor(current_key)
737
859
  end
738
860
  yield pair if pair && max && include_max && pair[0] == max
739
861
  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
862
+ start_node, stack = resolve_startup_asc(min, include_min)
863
+ traverse_from_asc(start_node, stack, max, include_max, &block)
765
864
  end
766
865
  end
767
866
 
768
867
  # Traverses the tree in descending order (reverse in-order traversal).
769
868
  #
770
- # @param node [Node] the current node
771
869
  # @param min [Object] the lower bound (inclusive)
772
870
  # @param max [Object] the upper bound (inclusive)
773
871
  # @param include_min [Boolean] whether to include the lower bound
@@ -775,164 +873,218 @@ class RBTree
775
873
  # @param safe [Boolean] whether to use safe traversal
776
874
  # @yield [key, value] each key-value pair in descending order
777
875
  # @return [void]
778
- def traverse_range_desc(node, min, max, include_min, include_max, safe: false, &block)
876
+ def traverse_range_desc(min, max, include_min, include_max, safe: false, &block)
877
+ # O(1) Range Rejection
878
+ return if max && @min_node != @nil_node && (max <=> @min_node.key) < 0
879
+ return if max && @min_node != @nil_node && !include_max && (max <=> @min_node.key) == 0
880
+
881
+ # O(1) Bound Optimization
882
+ min = nil if min && @min_node != @nil_node && (cmp = min <=> @min_node.key) <= 0 && (cmp < 0 || include_min)
883
+
779
884
  if safe
780
885
  pair = !max ? find_max :
781
886
  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
887
+ while pair && (!min || pair[0] > min)
888
+ current_key = pair[0]
889
+ yield pair
890
+ pair = find_predecessor(current_key)
794
891
  end
795
892
  yield pair if pair && min && include_min && pair[0] == min
796
893
  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
894
+ start_node, stack = resolve_startup_desc(max, include_max)
895
+ traverse_from_desc(start_node, stack, min, include_min, &block)
822
896
  end
823
897
  end
824
898
 
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.
899
+ private
900
+
901
+ # Returns the predecessor of the given node.
842
902
  #
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)
903
+ # @param node [Node] the node to find the predecessor of
904
+ # @return [Node] the predecessor node
905
+ def predecessor_node_of(node)
906
+ if node.left != @nil_node
907
+ return rightmost(node.left)
908
+ end
909
+ y = node.parent
910
+ while y != @nil_node && node == y.left
911
+ node = y
912
+ y = y.parent
913
+ end
914
+ y
915
+ end
849
916
 
850
- # Traverses nodes with keys less than or equal to the specified key.
917
+ # Returns the successor of the given node.
851
918
  #
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)
919
+ # @param node [Node] the node to find the successor of
920
+ # @return [Node] the successor node
921
+ def successor_node_of(node)
922
+ if node.right != @nil_node
923
+ return leftmost(node.right)
924
+ end
925
+ y = node.parent
926
+ while y != @nil_node && node == y.right
927
+ node = y
928
+ y = y.parent
929
+ end
930
+ y
931
+ end
858
932
 
859
- # Traverses nodes with keys greater than the specified key.
933
+ # Resolves the startup node for ascending traversal.
860
934
  #
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)
935
+ # @param min [Object] the minimum key to traverse from
936
+ # @param include_min [Boolean] whether to include the minimum key
937
+ # @return [Array(Node, Array)] the starting node and stack
938
+ def resolve_startup_asc(min, include_min)
939
+ # 1. Use cached min if no lower bound or if min is less than tree min
940
+ if @min_node != @nil_node && (!min || (min <=> @min_node.key) < 0)
941
+ return [@min_node, reconstruct_stack_asc(@min_node)]
942
+ end
943
+
944
+ # 2. Use Hash index if key exists
945
+ if min && (node = @hash_index[min])
946
+ start_node = include_min ? node : successor_node_of(node)
947
+ return [start_node, reconstruct_stack_asc(start_node)]
948
+ end
867
949
 
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)
950
+ # 3. Fallback to tree search from root
951
+ stack = []
952
+ current = @root
953
+ while current != @nil_node
954
+ if min && ((current.key <=> min) < 0 || (!include_min && (current.key <=> min) == 0))
955
+ current = current.right
956
+ else
957
+ stack << current
958
+ current = current.left
959
+ end
960
+ end
961
+ [@nil_node, stack]
962
+ end
876
963
 
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)
964
+ # Reconstructs the stack for ascending traversal.
965
+ #
966
+ # @param node [Node] the node to reconstruct the stack from
967
+ # @return [Array] the reconstructed stack
968
+ def reconstruct_stack_asc(node)
969
+ return [] if node == @nil_node
970
+ stack = []
971
+ curr = node
972
+ while (p = curr.parent) != @nil_node
973
+ stack << p if curr == p.left
974
+ curr = p
975
+ end
976
+ stack.reverse!
977
+ stack
978
+ end
888
979
 
889
- # Traverses nodes with keys less than the specified key in descending order.
980
+ # Traverses the tree in ascending order.
890
981
  #
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)
982
+ # @param current [Node] the current node
983
+ # @param stack [Array] the stack of nodes to traverse
984
+ # @param max [Object] the maximum key to traverse to
985
+ # @param include_max [Boolean] whether to include the maximum key
986
+ # @yield [Array(Object, Object)] each key-value pair
987
+ # @yieldparam key [Object] the key
988
+ # @yieldparam val [Object] the value
989
+ def traverse_from_asc(current, stack, max, include_max, &block)
990
+ while current != @nil_node || !stack.empty?
991
+ if current != @nil_node
992
+ if max
993
+ cmp = current.key <=> max
994
+ if cmp >= 0
995
+ yield current.pair if include_max && cmp == 0
996
+ return
997
+ end
998
+ end
999
+ yield current.pair
1000
+ current = current.right
1001
+ while current != @nil_node
1002
+ stack << current
1003
+ current = current.left
1004
+ end
1005
+ else
1006
+ current = stack.pop
1007
+ end
1008
+ end
1009
+ end
897
1010
 
898
- # Traverses nodes with keys less than or equal to the specified key in descending order.
1011
+ # Resolves the startup node for descending traversal.
899
1012
  #
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)
1013
+ # @param max [Object] the maximum key to traverse from
1014
+ # @param include_max [Boolean] whether to include the maximum key
1015
+ # @return [Array(Node, Array)] the starting node and stack
1016
+ def resolve_startup_desc(max, include_max)
1017
+ # 1. Use cached max if no upper bound or if max is greater than tree max
1018
+ if @max_node != @nil_node && (!max || (max <=> @max_node.key) > 0)
1019
+ return [@max_node, reconstruct_stack_desc(@max_node)]
1020
+ end
1021
+
1022
+ # 2. Use Hash index if key exists
1023
+ if max && (node = @hash_index[max])
1024
+ start_node = include_max ? node : predecessor_node_of(node)
1025
+ return [start_node, reconstruct_stack_desc(start_node)]
1026
+ end
906
1027
 
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)
1028
+ # 3. Fallback to tree search from root
1029
+ stack = []
1030
+ current = @root
1031
+ while current != @nil_node
1032
+ if max && ((current.key <=> max) > 0 || (!include_max && (current.key <=> max) == 0))
1033
+ current = current.left
1034
+ else
1035
+ stack << current
1036
+ current = current.right
1037
+ end
1038
+ end
1039
+ [@nil_node, stack]
1040
+ end
915
1041
 
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)
1042
+ # Reconstructs the stack for descending traversal.
1043
+ #
1044
+ # @param node [Node] the node to reconstruct the stack from
1045
+ # @return [Array] the reconstructed stack
1046
+ def reconstruct_stack_desc(node)
1047
+ return [] if node == @nil_node
1048
+ stack = []
1049
+ curr = node
1050
+ while (p = curr.parent) != @nil_node
1051
+ stack << p if curr == p.right
1052
+ curr = p
1053
+ end
1054
+ stack.reverse!
1055
+ stack
1056
+ end
924
1057
 
925
- # Traverses nodes with keys within the specified range in descending order.
1058
+ # Traverses the tree in descending order.
926
1059
  #
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)
1060
+ # @param current [Node] the current node
1061
+ # @param stack [Array] the stack of nodes to traverse
1062
+ # @param min [Object] the minimum key to traverse to
1063
+ # @param include_min [Boolean] whether to include the minimum key
1064
+ # @yield [Array(Object, Object)] each key-value pair
1065
+ # @yieldparam key [Object] the key
1066
+ # @yieldparam val [Object] the value
1067
+ def traverse_from_desc(current, stack, min, include_min, &block)
1068
+ while current != @nil_node || !stack.empty?
1069
+ if current != @nil_node
1070
+ if min
1071
+ cmp = current.key <=> min
1072
+ if cmp <= 0
1073
+ yield current.pair if include_min && cmp == 0
1074
+ return
1075
+ end
1076
+ end
1077
+ yield current.pair
1078
+ current = current.left
1079
+ while current != @nil_node
1080
+ stack << current
1081
+ current = current.right
1082
+ end
1083
+ else
1084
+ current = stack.pop
1085
+ end
1086
+ end
1087
+ end
936
1088
 
937
1089
  # Restores red-black tree properties after insertion.
938
1090
  #
@@ -1036,6 +1188,10 @@ class RBTree
1036
1188
  if next_min_node
1037
1189
  @min_node = next_min_node
1038
1190
  end
1191
+ if z == @max_node
1192
+ next_max_node = predecessor_node_of(z)
1193
+ @max_node = next_max_node
1194
+ end
1039
1195
 
1040
1196
  value = z.value
1041
1197
  release_node(z)
@@ -1128,6 +1284,13 @@ class RBTree
1128
1284
  # @param key [Numeric] the target key
1129
1285
  # @return [Node] the nearest node, or @nil_node if tree is empty
1130
1286
  def find_nearest_node(key)
1287
+ raise ArgumentError, "key must be Numeric" unless key.is_a?(Numeric)
1288
+
1289
+ # If key is larger than max_key, return max_node
1290
+ return @max_node if @max_node != @nil_node && key >= @max_node.key
1291
+ # If key is smaller than min_key, return min_node
1292
+ return @min_node if @min_node != @nil_node && key <= @min_node.key
1293
+
1131
1294
  current = @root
1132
1295
  closest = @nil_node
1133
1296
  min_dist = nil
@@ -1168,21 +1331,11 @@ class RBTree
1168
1331
  # @param key [Object] the reference key
1169
1332
  # @return [Node] the predecessor node, or @nil_node if none exists
1170
1333
  def find_predecessor_node(key)
1171
- # Check if key exists using O(1) hash lookup
1334
+ # If key is larger than max_key, return max_node
1335
+ return @max_node if max_key && (key <=> max_key) > 0
1336
+ # If key exists using O(1) hash lookup, return predecessor node
1172
1337
  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
1338
+ return predecessor_node_of(node)
1186
1339
  end
1187
1340
 
1188
1341
  # Key doesn't exist: descend tree tracking the best candidate
@@ -1218,25 +1371,14 @@ class RBTree
1218
1371
  # @param key [Object] the reference key
1219
1372
  # @return [Node] the successor node, or @nil_node if none exists
1220
1373
  def find_successor_node(key)
1374
+ # If key is larger than or equal to max_key, return nil
1375
+ return @nil_node if max_key && (key <=> max_key) >= 0
1221
1376
  # 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
1377
+ return @min_node if min_key && (key <=> min_key) < 0
1378
+ # If key exists using O(1) hash lookup, return successor node
1224
1379
  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
1380
+ return successor_node_of(node)
1238
1381
  end
1239
-
1240
1382
  # Key doesn't exist: descend tree tracking the best candidate
1241
1383
  current = @root
1242
1384
  successor = @nil_node
@@ -1292,7 +1434,7 @@ class RBTree
1292
1434
  # Returns the maximum key-value pair.
1293
1435
  #
1294
1436
  # @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
1437
+ def find_max = ((n = @max_node) != @nil_node) && n.pair
1296
1438
 
1297
1439
  # Performs a left rotation on the given node.
1298
1440
  #
@@ -1477,6 +1619,13 @@ class MultiRBTree < RBTree
1477
1619
  # Returns the number of values stored in the tree.
1478
1620
  # @return [Integer] the number of values in the tree
1479
1621
  def size = @value_count
1622
+
1623
+ # Removes all elements from the tree.
1624
+ # @return [MultiRBTree] self
1625
+ def clear
1626
+ @value_count = 0
1627
+ super
1628
+ end
1480
1629
 
1481
1630
  # Returns the minimum key-value pair without removing it.
1482
1631
  #
@@ -1679,9 +1828,46 @@ class MultiRBTree < RBTree
1679
1828
  [key, val]
1680
1829
  end
1681
1830
 
1831
+ # Keeps key-value pairs for which the block returns true, deleting the rest.
1832
+ # Unlike RBTree, this removes individual values rather than entire keys.
1833
+ #
1834
+ # @yield [key, value] each key-value pair
1835
+ # @return [MultiRBTree, Enumerator] self, or Enumerator if no block
1836
+ def keep_if(&block)
1837
+ return enum_for(__method__) { size } unless block_given?
1838
+ filter_values! { |k, v| block.call(k, v) }
1839
+ self
1840
+ end
1841
+
1842
+ # Deletes key-value pairs for which the block returns true.
1843
+ # Unlike RBTree, this removes individual values rather than entire keys.
1844
+ #
1845
+ # @yield [key, value] each key-value pair
1846
+ # @return [MultiRBTree, Enumerator] self, or Enumerator if no block
1847
+ def delete_if(&block)
1848
+ return enum_for(__method__) { size } unless block_given?
1849
+ filter_values! { |k, v| !block.call(k, v) }
1850
+ self
1851
+ end
1852
+
1682
1853
  # @!visibility private
1683
1854
  private
1684
1855
 
1856
+ # Filters values in-place across all nodes.
1857
+ # Keeps only values for which the block returns true.
1858
+ # Removes nodes whose value arrays become empty.
1859
+ # Updates @value_count accordingly.
1860
+ def filter_values!
1861
+ keys_to_delete = []
1862
+ @hash_index.each do |key, node|
1863
+ before = node.value.size
1864
+ node.value.select! { |v| yield key, v }
1865
+ @value_count -= before - node.value.size
1866
+ keys_to_delete << key if node.value.empty?
1867
+ end
1868
+ keys_to_delete.each { |k| delete_indexed_node(k) }
1869
+ end
1870
+
1685
1871
  # Inserts a value for the given key.
1686
1872
  #
1687
1873
  # If the key already exists, the value is appended to its list.
@@ -1696,47 +1882,15 @@ class MultiRBTree < RBTree
1696
1882
  # tree.insert(1, 'first')
1697
1883
  # tree.insert(1, 'second') # adds another value for key 1
1698
1884
  def insert_entry(key, value, **)
1699
- if (node = @hash_index[key])
1700
- node.value << value
1885
+ insert_entry_generic(key) do |node, is_new|
1701
1886
  @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
1887
+ if is_new
1888
+ [value]
1715
1889
  else
1716
- x = x.right
1890
+ node.value << value
1891
+ true
1717
1892
  end
1718
1893
  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
1894
  end
1741
1895
 
1742
1896
  # 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.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahito Suzuki