rbtree-ruby 0.2.2 → 0.2.3
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 +10 -0
- data/README.ja.md +12 -6
- data/README.md +8 -4
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +119 -68
- 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: f3c97bfa59aaaa7bbc03f272fdc5e383b258f529134f1d2a79fd3758d1d53b02
|
|
4
|
+
data.tar.gz: fa9c8df14da95bf4224164d720c90981c1c68ef96e5cb544007327b9a1564b11
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e856b4d10d326c3d3261d21044db760b41447dd01e1eb22bff4dfe1ac07e71f709792b75a9ee48ddc3195f06993fd86e8434511b3f2f0eaafbcf0827da05aa1e
|
|
7
|
+
data.tar.gz: 10e8c1af8787b201c73a64557ba86304867b601daae13ad816771b87e52a81e30bc94dbeda5accc1e224a55d397ae28e0325918d61d8d4db40ee09cdd83876fa
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.2.3] - 2026-01-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Bulk Insert**: `insert` now supports bulk insertion from Hash, Array of pairs, Enumerator, or Block.
|
|
12
|
+
- `tree.insert({1 => 'one', 2 => 'two'})`
|
|
13
|
+
- `tree.insert([[1, 'one'], [2, 'two']])`
|
|
14
|
+
- `tree.insert { source_data }`
|
|
15
|
+
- **Flexible Initialization**: `RBTree.new` and `MultiRBTree.new` now accept bulk data and an `overwrite:` option.
|
|
16
|
+
- `RBTree.new([[1, 'a'], [1, 'b']], overwrite: false)`
|
|
17
|
+
|
|
8
18
|
## [0.2.2] - 2026-01-14
|
|
9
19
|
|
|
10
20
|
### Changed
|
data/README.ja.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
🌍 *[English](README.md) | [日本語](README.ja.md)*
|
|
4
4
|
|
|
5
|
-
Red-Black Tree(赤黒木)データ構造のピュアRuby実装です。挿入、削除、検索操作がO(log n)
|
|
6
|
-
効率的な順序付きキーバリューストレージを提供します。
|
|
5
|
+
Red-Black Tree(赤黒木)データ構造のピュアRuby実装です。挿入、削除、検索操作がO(log n)の時間計算量で実行できる、効率的な順序付きキーバリューストレージを提供します。
|
|
7
6
|
|
|
8
7
|
## 特徴
|
|
9
8
|
|
|
@@ -43,13 +42,18 @@ require 'rbtree'
|
|
|
43
42
|
# 空のツリーを作成
|
|
44
43
|
tree = RBTree.new
|
|
45
44
|
|
|
46
|
-
#
|
|
45
|
+
# データで初期化(バルク挿入)
|
|
47
46
|
tree = RBTree.new({3 => 'three', 1 => 'one', 2 => 'two'})
|
|
48
47
|
tree = RBTree[[5, 'five'], [4, 'four']]
|
|
48
|
+
tree = RBTree.new do # ブロック初期化
|
|
49
|
+
data_source.each { |data| [data.time, data.content] }
|
|
50
|
+
end
|
|
49
51
|
|
|
50
52
|
# 値の挿入と取得
|
|
51
53
|
tree.insert(10, 'ten')
|
|
52
54
|
tree[20] = 'twenty'
|
|
55
|
+
# バルク挿入
|
|
56
|
+
tree.insert({30 => 'thirty', 40 => 'forty'})
|
|
53
57
|
puts tree[10] # => "ten"
|
|
54
58
|
|
|
55
59
|
# ソート順でイテレーション
|
|
@@ -138,6 +142,8 @@ tree.nearest(8) # => [10, "ten"]
|
|
|
138
142
|
|
|
139
143
|
### 前後キー検索
|
|
140
144
|
|
|
145
|
+
ツリーの中で次のキーまたは前のキーを検索します。
|
|
146
|
+
|
|
141
147
|
```ruby
|
|
142
148
|
tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
|
|
143
149
|
|
|
@@ -202,8 +208,8 @@ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
|
|
|
202
208
|
|
|
203
209
|
- `insert(key, value)` - O(log n)
|
|
204
210
|
- `delete(key)` - O(log n)
|
|
205
|
-
- `get(key)` / `[]` - **O(1)** (
|
|
206
|
-
- `has_key?` - **O(1)** (
|
|
211
|
+
- `get(key)` / `[]` - **O(1)** (内部ハッシュインデックスによる超高速アクセス)
|
|
212
|
+
- `has_key?` - **O(1)** (内部ハッシュインデックスによる超高速チェック)
|
|
207
213
|
- `min` - **O(1)**
|
|
208
214
|
- `max` - O(log n)
|
|
209
215
|
- `shift` / `pop` - O(log n)
|
|
@@ -227,7 +233,7 @@ RBTreeは内部的な**メモリプール**を使用してノードオブジェ
|
|
|
227
233
|
| **範囲クエリ** | **O(log n + k)** | O(n) フィルター | **〜540倍高速** | 部分木へ直接ジャンプ vs 全件スキャン |
|
|
228
234
|
| **最小値抽出** | **O(log n)** | O(n) 検索 | **〜160倍高速** | 連続的なリバランス vs 全件スキャン |
|
|
229
235
|
| **ソート済みイテレーション** | **O(n)** | O(n log n) | **無料** | 常にソート済み vs 明示的な`sort` |
|
|
230
|
-
| **キー検索** | **O(1)** | O(1) | **同等** |
|
|
236
|
+
| **キー検索** | **O(1)** | O(1) | **同等** | **ハイブリッドハッシュインデックスにより、Hashと同等のO(1)検索速度を実現** |
|
|
231
237
|
|
|
232
238
|
### RBTreeを使うべき場面
|
|
233
239
|
|
data/README.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
🌍 *[English](README.md) | [日本語](README.ja.md)*
|
|
4
4
|
|
|
5
|
-
A pure Ruby implementation of the Red-Black Tree data structure, providing efficient ordered
|
|
6
|
-
key-value storage with O(log n) time complexity for insertion, deletion, and lookup operations.
|
|
5
|
+
A pure Ruby implementation of the Red-Black Tree data structure, providing efficient ordered key-value storage with O(log n) time complexity for insertion, deletion, and lookup operations.
|
|
7
6
|
|
|
8
7
|
## Features
|
|
9
8
|
|
|
@@ -43,13 +42,18 @@ require 'rbtree'
|
|
|
43
42
|
# Create an empty tree
|
|
44
43
|
tree = RBTree.new
|
|
45
44
|
|
|
46
|
-
# Or initialize with data
|
|
45
|
+
# Or initialize with data (Bulk Insert)
|
|
47
46
|
tree = RBTree.new({3 => 'three', 1 => 'one', 2 => 'two'})
|
|
48
47
|
tree = RBTree[[5, 'five'], [4, 'four']]
|
|
48
|
+
tree = RBTree.new do # Block initialization
|
|
49
|
+
data_source.each { |data| [data.time, data.content] }
|
|
50
|
+
end
|
|
49
51
|
|
|
50
52
|
# Insert and retrieve values
|
|
51
53
|
tree.insert(10, 'ten')
|
|
52
54
|
tree[20] = 'twenty'
|
|
55
|
+
# Bulk insert
|
|
56
|
+
tree.insert({30 => 'thirty', 40 => 'forty'})
|
|
53
57
|
puts tree[10] # => "ten"
|
|
54
58
|
|
|
55
59
|
# Iterate in sorted order
|
|
@@ -229,7 +233,7 @@ For ordered and spatial operations, RBTree is not just faster—it is in a compl
|
|
|
229
233
|
| **Range Queries** | **O(log n + k)** | O(n) filter | **~540x faster** | Direct subtree jump vs full scan |
|
|
230
234
|
| **Min Extraction** | **O(log n)** | O(n) search | **~160x faster** | Continuous rebalancing vs full scan |
|
|
231
235
|
| **Sorted Iteration** | **O(n)** | O(n log n) | **FREE** | Always sorted vs explicit `sort` |
|
|
232
|
-
| **Key Lookup** | **O(1)** | O(1) | **Equal** |
|
|
236
|
+
| **Key Lookup** | **O(1)** | O(1) | **Equal** | **Hybrid Hash Index provides O(1) access like standard Hash** |
|
|
233
237
|
|
|
234
238
|
### When to Use RBTree
|
|
235
239
|
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -72,11 +72,15 @@ class RBTree
|
|
|
72
72
|
|
|
73
73
|
# Initializes a new RBTree.
|
|
74
74
|
#
|
|
75
|
-
# The tree can be initialized empty or populated with initial data from a Hash or
|
|
75
|
+
# The tree can be initialized empty or populated with initial data from a Hash, Array, or Enumerator.
|
|
76
|
+
# A block can also be provided to supply the initial data.
|
|
76
77
|
#
|
|
77
78
|
# @param args [Hash, Array, nil] optional initial data
|
|
79
|
+
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
80
|
+
# @yieldreturn [Object] optional initial data
|
|
78
81
|
# - If a Hash is provided, each key-value pair is inserted into the tree
|
|
79
82
|
# - If an Array is provided, it should contain [key, value] pairs
|
|
83
|
+
# - If a block is provided, it is yielded to get the source data
|
|
80
84
|
# - If no arguments are provided, an empty tree is created
|
|
81
85
|
# @raise [ArgumentError] if arguments are invalid
|
|
82
86
|
# @example Create an empty tree
|
|
@@ -85,7 +89,9 @@ class RBTree
|
|
|
85
89
|
# tree = RBTree.new({1 => 'one', 2 => 'two'})
|
|
86
90
|
# @example Create from an array
|
|
87
91
|
# tree = RBTree.new([[1, 'one'], [2, 'two']])
|
|
88
|
-
|
|
92
|
+
# @example Create with overwrite: false
|
|
93
|
+
# tree = RBTree.new([[1, 'one'], [1, 'uno']], overwrite: false)
|
|
94
|
+
def initialize(*args, overwrite: true, &block)
|
|
89
95
|
@nil_node = Node.new
|
|
90
96
|
@nil_node.color = Node::BLACK
|
|
91
97
|
@nil_node.left = @nil_node
|
|
@@ -96,19 +102,8 @@ class RBTree
|
|
|
96
102
|
@node_pool = [] # Memory pool for recycling nodes
|
|
97
103
|
@size = 0
|
|
98
104
|
|
|
99
|
-
if args.
|
|
100
|
-
|
|
101
|
-
case source
|
|
102
|
-
when Hash
|
|
103
|
-
source.each { |k, v| insert(k, v) }
|
|
104
|
-
when Array
|
|
105
|
-
source.each do |arg|
|
|
106
|
-
key, value = arg
|
|
107
|
-
insert(key, value)
|
|
108
|
-
end
|
|
109
|
-
else
|
|
110
|
-
raise ArgumentError, "Invalid arguments"
|
|
111
|
-
end
|
|
105
|
+
if args.size > 0 || block_given?
|
|
106
|
+
insert(*args, overwrite: overwrite, &block)
|
|
112
107
|
end
|
|
113
108
|
end
|
|
114
109
|
|
|
@@ -211,63 +206,67 @@ class RBTree
|
|
|
211
206
|
n == @nil_node ? nil : n.pair
|
|
212
207
|
end
|
|
213
208
|
|
|
214
|
-
# Inserts or
|
|
209
|
+
# Inserts one or more key-value pairs into the tree.
|
|
210
|
+
#
|
|
211
|
+
# This method supports both single entry insertion and bulk insertion.
|
|
212
|
+
#
|
|
213
|
+
# Single insertion:
|
|
214
|
+
# insert(key, value, overwrite: true)
|
|
215
|
+
#
|
|
216
|
+
# Bulk insertion:
|
|
217
|
+
# insert(hash, overwrite: true)
|
|
218
|
+
# insert(array_of_pairs, overwrite: true)
|
|
219
|
+
# insert(enumerator, overwrite: true)
|
|
220
|
+
# insert { data_source }
|
|
215
221
|
#
|
|
216
222
|
# If the key already exists and overwrite is true (default), the value is updated.
|
|
217
223
|
# If overwrite is false and the key exists, the operation returns nil without modification.
|
|
218
224
|
#
|
|
219
|
-
# @param
|
|
220
|
-
# @param value [Object] the value to associate with the key
|
|
225
|
+
# @param args [Object] key (and value) or source object
|
|
221
226
|
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
222
|
-
# @
|
|
223
|
-
# @
|
|
224
|
-
#
|
|
225
|
-
# tree.insert(1, 'one')
|
|
226
|
-
#
|
|
227
|
-
# tree.insert(1
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return
|
|
242
|
-
x.value = value
|
|
243
|
-
return true
|
|
244
|
-
elsif cmp < 0
|
|
245
|
-
x = x.left
|
|
227
|
+
# @yieldreturn [Object] data source for bulk insertion
|
|
228
|
+
# @return [Boolean, nil] true if inserted/updated, nil if key exists and overwrite is false (for single insert)
|
|
229
|
+
# @example Single insert
|
|
230
|
+
# tree.insert(1, 'one')
|
|
231
|
+
# @example Bulk insert from Hash
|
|
232
|
+
# tree.insert({1 => 'one', 2 => 'two'})
|
|
233
|
+
# @example Bulk insert from Array
|
|
234
|
+
# tree.insert([[1, 'one'], [2, 'two']])
|
|
235
|
+
def insert(*args, overwrite: true, &block)
|
|
236
|
+
if args.size == 2
|
|
237
|
+
key, value = args
|
|
238
|
+
insert_entry(key, value, overwrite: overwrite)
|
|
239
|
+
else
|
|
240
|
+
source = nil
|
|
241
|
+
if args.empty? && block_given?
|
|
242
|
+
source = yield
|
|
243
|
+
elsif args.size == 1
|
|
244
|
+
source = args[0]
|
|
245
|
+
elsif args.empty?
|
|
246
|
+
return # No-op
|
|
246
247
|
else
|
|
247
|
-
|
|
248
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..2)"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
return if source.nil?
|
|
252
|
+
|
|
253
|
+
unless source.respond_to?(:each)
|
|
254
|
+
raise ArgumentError, "Source must be iterable"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
source.each do |*pair|
|
|
258
|
+
key, value = nil, nil
|
|
259
|
+
if pair.size == 1 && pair[0].is_a?(Array)
|
|
260
|
+
key, value = pair[0]
|
|
261
|
+
raise ArgumentError, "Invalid pair size: #{pair[0].size} (expected 2)" unless pair[0].size == 2
|
|
262
|
+
elsif pair.size == 2
|
|
263
|
+
key, value = pair
|
|
264
|
+
else
|
|
265
|
+
raise ArgumentError, "Invalid pair format: #{pair.inspect}"
|
|
266
|
+
end
|
|
267
|
+
insert_entry(key, value, overwrite: overwrite)
|
|
248
268
|
end
|
|
249
269
|
end
|
|
250
|
-
z = allocate_node(key, value, Node::RED, @nil_node, @nil_node, @nil_node)
|
|
251
|
-
z.parent = y
|
|
252
|
-
if y == @nil_node
|
|
253
|
-
@root = z
|
|
254
|
-
elsif (key <=> y.key) < 0
|
|
255
|
-
y.left = z
|
|
256
|
-
else
|
|
257
|
-
y.right = z
|
|
258
|
-
end
|
|
259
|
-
z.left = @nil_node
|
|
260
|
-
z.right = @nil_node
|
|
261
|
-
z.color = Node::RED
|
|
262
|
-
insert_fixup(z)
|
|
263
|
-
@size += 1
|
|
264
|
-
|
|
265
|
-
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
266
|
-
@min_node = z
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
@hash_index[key] = z # Add to hash index
|
|
270
|
-
true
|
|
271
270
|
end
|
|
272
271
|
alias_method :[]=, :insert
|
|
273
272
|
|
|
@@ -518,8 +517,58 @@ class RBTree
|
|
|
518
517
|
true
|
|
519
518
|
end
|
|
520
519
|
|
|
521
|
-
# @!visibility
|
|
522
|
-
|
|
520
|
+
# @!visibility protected
|
|
521
|
+
protected
|
|
522
|
+
|
|
523
|
+
# Inserts a single key-value pair.
|
|
524
|
+
#
|
|
525
|
+
# @param key [Object] the key to insert
|
|
526
|
+
# @param value [Object] the value to associate with the key
|
|
527
|
+
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
528
|
+
# @return [Boolean, nil] true if inserted/updated, nil if key exists and overwrite is false
|
|
529
|
+
def insert_entry(key, value, overwrite: true)
|
|
530
|
+
if (node = @hash_index[key])
|
|
531
|
+
return nil unless overwrite
|
|
532
|
+
node.value = value
|
|
533
|
+
return true
|
|
534
|
+
end
|
|
535
|
+
y = @nil_node
|
|
536
|
+
x = @root
|
|
537
|
+
while x != @nil_node
|
|
538
|
+
y = x
|
|
539
|
+
cmp = key <=> x.key
|
|
540
|
+
if cmp == 0
|
|
541
|
+
return nil unless overwrite
|
|
542
|
+
x.value = value
|
|
543
|
+
return true
|
|
544
|
+
elsif cmp < 0
|
|
545
|
+
x = x.left
|
|
546
|
+
else
|
|
547
|
+
x = x.right
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
z = allocate_node(key, value, Node::RED, @nil_node, @nil_node, @nil_node)
|
|
551
|
+
z.parent = y
|
|
552
|
+
if y == @nil_node
|
|
553
|
+
@root = z
|
|
554
|
+
elsif (key <=> y.key) < 0
|
|
555
|
+
y.left = z
|
|
556
|
+
else
|
|
557
|
+
y.right = z
|
|
558
|
+
end
|
|
559
|
+
z.left = @nil_node
|
|
560
|
+
z.right = @nil_node
|
|
561
|
+
z.color = Node::RED
|
|
562
|
+
insert_fixup(z)
|
|
563
|
+
@size += 1
|
|
564
|
+
|
|
565
|
+
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
566
|
+
@min_node = z
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
@hash_index[key] = z # Add to hash index
|
|
570
|
+
true
|
|
571
|
+
end
|
|
523
572
|
|
|
524
573
|
# Traverses the tree in ascending order (in-order traversal).
|
|
525
574
|
#
|
|
@@ -1377,12 +1426,13 @@ class MultiRBTree < RBTree
|
|
|
1377
1426
|
#
|
|
1378
1427
|
# @param key [Object] the key (must implement <=>)
|
|
1379
1428
|
# @param value [Object] the value to insert
|
|
1429
|
+
# @param overwrite [Boolean] ignored for MultiRBTree which always appends
|
|
1380
1430
|
# @return [Boolean] always returns true
|
|
1381
1431
|
# @example
|
|
1382
1432
|
# tree = MultiRBTree.new
|
|
1383
1433
|
# tree.insert(1, 'first')
|
|
1384
1434
|
# tree.insert(1, 'second') # adds another value for key 1
|
|
1385
|
-
def
|
|
1435
|
+
def insert_entry(key, value, **)
|
|
1386
1436
|
if (node = @hash_index[key])
|
|
1387
1437
|
node.value << value
|
|
1388
1438
|
@size += 1
|
|
@@ -1527,7 +1577,8 @@ class MultiRBTree < RBTree
|
|
|
1527
1577
|
@hash_index[key]&.value&.each { |v| yield v }
|
|
1528
1578
|
end
|
|
1529
1579
|
|
|
1530
|
-
|
|
1580
|
+
# @!visibility protected
|
|
1581
|
+
protected
|
|
1531
1582
|
|
|
1532
1583
|
def traverse_range_asc(...)
|
|
1533
1584
|
super { |k, vals| vals.each { |v| yield k, v } }
|