rbtree-ruby 0.3.5 → 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 +4 -4
- data/CHANGELOG.md +23 -0
- data/README.ja.md +49 -1
- data/README.md +49 -1
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +150 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18d0cdfd5f115b61096c266d9de0a482e263574aad1dfce425efaabd3dad67b7
|
|
4
|
+
data.tar.gz: 639a4f4f85c14cab4acf93f5b9b5280fb6225c96c575cac1a47b2b4f934dca8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e40c1a174c7f2dd1c6df7f94e316070178457e0baa41322baa0f622157eb548245bfd334435488d3a49cfe8396e8d587ae80f886a17dadbf93cd9f03fcd701d7
|
|
7
|
+
data.tar.gz: 20f49f91135a98e4b46ccb4b0981dafab6bb35a9375bfb06024b7b42f5c71ad04b23613ad5a1f372ac6456d9edf03ef9698b68ca2228e0cf5cb85d6c2ae2b5f4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ 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
|
+
|
|
8
30
|
## [0.3.5] - 2026-01-26
|
|
9
31
|
|
|
10
32
|
### Optimized
|
|
@@ -230,6 +252,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
230
252
|
- ASCII diagrams for tree rotation operations
|
|
231
253
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
232
254
|
|
|
255
|
+
[0.4.0]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.4.0
|
|
233
256
|
[0.3.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.5
|
|
234
257
|
[0.3.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.4
|
|
235
258
|
[0.3.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.3
|
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
|
-
#
|
|
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 値配列アクセス
|
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
|
|
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
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -111,6 +111,16 @@ class RBTree
|
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
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
|
+
|
|
114
124
|
# Returns a Hash containing all key-value pairs from the tree.
|
|
115
125
|
#
|
|
116
126
|
# @return [Hash] a new Hash with the tree's contents
|
|
@@ -369,16 +379,43 @@ class RBTree
|
|
|
369
379
|
end
|
|
370
380
|
alias :[]= :insert
|
|
371
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
|
+
|
|
372
394
|
# Merges the contents of another tree, hash, or enumerable into this tree.
|
|
373
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
|
+
#
|
|
374
399
|
# @param other [RBTree, Hash, Enumerable] the source to merge from
|
|
375
|
-
# @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
|
|
376
402
|
# @return [RBTree] self
|
|
377
|
-
def merge!(other, overwrite: true)
|
|
403
|
+
def merge!(other, overwrite: true, &block)
|
|
378
404
|
if defined?(MultiRBTree) && other.is_a?(MultiRBTree)
|
|
379
405
|
raise ArgumentError, "Cannot merge MultiRBTree into RBTree"
|
|
380
406
|
end
|
|
381
|
-
|
|
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
|
|
382
419
|
self
|
|
383
420
|
end
|
|
384
421
|
|
|
@@ -602,6 +639,72 @@ class RBTree
|
|
|
602
639
|
self
|
|
603
640
|
end
|
|
604
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) }
|
|
682
|
+
self
|
|
683
|
+
end
|
|
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
|
+
|
|
605
708
|
# Returns a string representation of the tree.
|
|
606
709
|
#
|
|
607
710
|
# Shows the first 5 entries and total size. Useful for debugging.
|
|
@@ -1516,6 +1619,13 @@ class MultiRBTree < RBTree
|
|
|
1516
1619
|
# Returns the number of values stored in the tree.
|
|
1517
1620
|
# @return [Integer] the number of values in the tree
|
|
1518
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
|
|
1519
1629
|
|
|
1520
1630
|
# Returns the minimum key-value pair without removing it.
|
|
1521
1631
|
#
|
|
@@ -1718,9 +1828,46 @@ class MultiRBTree < RBTree
|
|
|
1718
1828
|
[key, val]
|
|
1719
1829
|
end
|
|
1720
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
|
+
|
|
1721
1853
|
# @!visibility private
|
|
1722
1854
|
private
|
|
1723
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
|
+
|
|
1724
1871
|
# Inserts a value for the given key.
|
|
1725
1872
|
#
|
|
1726
1873
|
# If the key already exists, the value is appended to its list.
|