rbtree-ruby 0.3.0 → 0.3.2
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 +17 -1
- data/README.ja.md +36 -6
- data/README.md +36 -6
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +292 -73
- metadata +1 -2
- data/lib/rbtree.old.rb +0 -1588
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 327862d13e55ed6406023e1d10676e4b5e94541c59400d9205380af08822e4bf
|
|
4
|
+
data.tar.gz: 465e1a514390286bf0165db4dfa9f25ada4cd3d1410b0115b11eba15b03dcf33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f354523b8fb78b746cf256949887b89184ce1dcd190403a7024949cded391de24ce6f932720a58ae24592b4b453f3901b1896c9904f546aa03fe4b1524698c2
|
|
7
|
+
data.tar.gz: e1219a20ea771f3fe1ca2fc160d737c787a1a0e81cb5389b63f320dcd1d2ac9d2cc71d9df2b9698d73afc98069f76d39285ca8106691f0025f1d5a5fa8b6bf91
|
data/CHANGELOG.md
CHANGED
|
@@ -5,9 +5,23 @@ 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.2] - 2026-01-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Custom Node Allocator**: `RBTree#initialize` now accepts a `node_allocator:` keyword argument, allowing customization of the node pooling strategy.
|
|
12
|
+
- **AutoShrinkNodePool**: Introduced `RBTree::AutoShrinkNodePool` as the default allocator. It automatically releases memory back to the GC when the pool size exceeds the fluctuation range of recent active node counts, preventing memory bloat in long-running processes with variable loads.
|
|
13
|
+
- **Explicit Overwrite**: `overwrite: true` is now the explicit default for `initialize` and `insert`.
|
|
14
|
+
|
|
15
|
+
## [0.3.1] - 2026-01-15
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`to_h`**: Added `to_h` method to `RBTree` and `MultiRBTree` for efficient conversion to Hash.
|
|
19
|
+
- **`merge!`**: Added `merge!` method to `RBTree` and `MultiRBTree`.
|
|
20
|
+
- `RBTree#merge!`: Supports `overwrite` option (default: `true`). Raises `ArgumentError` if merging a `MultiRBTree` (incompatible value structures).
|
|
21
|
+
- `MultiRBTree#merge!`: Always appends values from the source.
|
|
22
|
+
|
|
8
23
|
## [0.3.0] - 2026-01-15
|
|
9
24
|
|
|
10
|
-
### Changed
|
|
11
25
|
### API Changes (Method Renaming & Deprecation)
|
|
12
26
|
> [!WARNING]
|
|
13
27
|
> The public API has been refactored to improve consistency. The following methods have been renamed.
|
|
@@ -184,6 +198,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
184
198
|
- ASCII diagrams for tree rotation operations
|
|
185
199
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
186
200
|
|
|
201
|
+
[0.3.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.2
|
|
202
|
+
[0.3.1]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.1
|
|
187
203
|
[0.3.0]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.3.0
|
|
188
204
|
[0.2.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.2.3
|
|
189
205
|
[0.2.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.2.2
|
data/README.ja.md
CHANGED
|
@@ -174,6 +174,25 @@ tree.lt(3).first # => [1, "one"] (遅延評価、配列は作
|
|
|
174
174
|
tree.gt(0).lazy.take(2).to_a # => [[1, "one"], [2, "two"]] (最初の2件のみ計算)
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
### 変換と結合
|
|
178
|
+
|
|
179
|
+
標準のRubyオブジェクトへの変換や、他のコレクションとの結合も簡単です:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
tree = RBTree.new({1 => 'one', 2 => 'two'})
|
|
183
|
+
|
|
184
|
+
# 配列への変換(Enumerable経由)
|
|
185
|
+
tree.to_a # => [[1, "one"], [2, "two"]]
|
|
186
|
+
|
|
187
|
+
# ハッシュへの変換
|
|
188
|
+
tree.to_h # => {1 => "one", 2 => "two"}
|
|
189
|
+
|
|
190
|
+
# 他のツリー、ハッシュ、またはEnumerableの結合
|
|
191
|
+
other = {3 => 'three'}
|
|
192
|
+
tree.merge!(other)
|
|
193
|
+
tree.size # => 3
|
|
194
|
+
```
|
|
195
|
+
|
|
177
196
|
### MultiRBTree 値配列アクセス
|
|
178
197
|
|
|
179
198
|
複数の値を持つキーで、どの値にアクセスするか選択:
|
|
@@ -217,12 +236,6 @@ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
|
|
|
217
236
|
|
|
218
237
|
全要素のイテレーションはO(n)時間。
|
|
219
238
|
|
|
220
|
-
### メモリ効率
|
|
221
|
-
|
|
222
|
-
RBTreeは内部的な**メモリプール**を使用してノードオブジェクトを再利用:
|
|
223
|
-
- 頻繁な挿入・削除時のGC負荷を大幅に削減
|
|
224
|
-
- 100,000回の循環操作ベンチマークで**GC時間0.0秒**を達成
|
|
225
|
-
|
|
226
239
|
### RBTree vs Hash vs Array
|
|
227
240
|
|
|
228
241
|
順序付き操作と空間的操作において、RBTreeは単に速いだけでなく、全く異なるクラスの性能を発揮。**50万件**でのベンチマーク:
|
|
@@ -235,6 +248,23 @@ RBTreeは内部的な**メモリプール**を使用してノードオブジェ
|
|
|
235
248
|
| **ソート済みイテレーション** | **O(n)** | O(n log n) | **無料** | 常にソート済み vs 明示的な`sort` |
|
|
236
249
|
| **キー検索** | **O(1)** | O(1) | **同等** | **ハイブリッドハッシュインデックスにより、Hashと同等のO(1)検索速度を実現** |
|
|
237
250
|
|
|
251
|
+
### メモリ効率とカスタムアロケータ
|
|
252
|
+
|
|
253
|
+
RBTreeは内部的な**メモリプール**を使用してノードオブジェクトを再利用します。
|
|
254
|
+
- 頻繁な挿入・削除時のGC負荷を大幅に削減します。
|
|
255
|
+
- **自動縮小**: デフォルトの `AutoShrinkNodePool` は、現在の使用量に対してプールが大きくなりすぎた場合、自動的に未使用ノードをRubyのGCに解放します。これにより、負荷が変動する長時間実行アプリケーションでのメモリ肥大化を防ぎます。
|
|
256
|
+
- **カスタマイズ**: プールの動作をカスタマイズしたり、独自のアロケータを指定することができます:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
# 自動縮小パラメータのカスタマイズ
|
|
260
|
+
pool = RBTree::AutoShrinkNodePool.new(
|
|
261
|
+
history_size: 60, # 60秒の履歴
|
|
262
|
+
buffer_factor: 1.5, # 変動幅の50%をバッファとして保持
|
|
263
|
+
reserve_ratio: 0.2 # 常に最大値の20%を予備として保持
|
|
264
|
+
)
|
|
265
|
+
tree = RBTree.new(node_allocator: pool)
|
|
266
|
+
```
|
|
267
|
+
|
|
238
268
|
### RBTreeを使うべき場面
|
|
239
269
|
|
|
240
270
|
✅ **RBTreeが適している場合:**
|
data/README.md
CHANGED
|
@@ -174,6 +174,25 @@ tree.lt(3).first # => [1, "one"] (lazy, no array created)
|
|
|
174
174
|
tree.gt(0).lazy.take(2).to_a # => [[1, "one"], [2, "two"]] (only computes first 2)
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
+
### Conversion and Merging
|
|
178
|
+
|
|
179
|
+
Seamlessly convert to standard Ruby objects or merge other collections:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
tree = RBTree.new({1 => 'one', 2 => 'two'})
|
|
183
|
+
|
|
184
|
+
# Convert to Array (via Enumerable)
|
|
185
|
+
tree.to_a # => [[1, "one"], [2, "two"]]
|
|
186
|
+
|
|
187
|
+
# Convert to Hash
|
|
188
|
+
tree.to_h # => {1 => "one", 2 => "two"}
|
|
189
|
+
|
|
190
|
+
# Merge another tree, hash, or enumerable
|
|
191
|
+
other = {3 => 'three'}
|
|
192
|
+
tree.merge!(other)
|
|
193
|
+
tree.size # => 3
|
|
194
|
+
```
|
|
195
|
+
|
|
177
196
|
### MultiRBTree Value Array Access
|
|
178
197
|
|
|
179
198
|
For keys with multiple values, choose which value to access:
|
|
@@ -217,12 +236,6 @@ All major operations run in **O(log n)** time:
|
|
|
217
236
|
|
|
218
237
|
Iteration over all elements takes O(n) time.
|
|
219
238
|
|
|
220
|
-
### Memory Efficiency
|
|
221
|
-
|
|
222
|
-
RBTree uses an internal **Memory Pool** to recycle node objects.
|
|
223
|
-
- Significantly reduces Garbage Collection (GC) pressure during frequent insertions and deletions (e.g., in high-throughput queues).
|
|
224
|
-
- In benchmarks with 100,000 cyclic operations, **GC time was 0.0s** compared to significant pauses without pooling.
|
|
225
|
-
|
|
226
239
|
### RBTree vs Hash vs Array (Overwhelming Power)
|
|
227
240
|
|
|
228
241
|
For ordered and spatial operations, RBTree is not just faster—it is in a completely different class. The following benchmarks were conducted with **500,000 items**:
|
|
@@ -235,6 +248,23 @@ For ordered and spatial operations, RBTree is not just faster—it is in a compl
|
|
|
235
248
|
| **Sorted Iteration** | **O(n)** | O(n log n) | **FREE** | Always sorted vs explicit `sort` |
|
|
236
249
|
| **Key Lookup** | **O(1)** | O(1) | **Equal** | **Hybrid Hash Index provides O(1) access like standard Hash** |
|
|
237
250
|
|
|
251
|
+
### Memory Efficiency & Custom Allocators
|
|
252
|
+
|
|
253
|
+
RBTree uses an internal **Memory Pool** to recycle node objects.
|
|
254
|
+
- Significantly reduces Garbage Collection (GC) pressure during frequent insertions and deletions.
|
|
255
|
+
- **Auto-Shrinking**: The default `AutoShrinkNodePool` automatically releases unused nodes back to Ruby's GC when the pool gets too large relative to current usage, preventing memory leaks in long-running applications with fluctuating workloads.
|
|
256
|
+
- **Customization**: You can customize the pool behavior or provide your own allocator:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
# Customize auto-shrink parameters
|
|
260
|
+
pool = RBTree::AutoShrinkNodePool.new(
|
|
261
|
+
history_size: 60, # 1 minute history
|
|
262
|
+
buffer_factor: 1.5, # Keep 50% buffer above fluctuation
|
|
263
|
+
reserve_ratio: 0.2 # Always keep 20% reserve
|
|
264
|
+
)
|
|
265
|
+
tree = RBTree.new(node_allocator: pool)
|
|
266
|
+
```
|
|
267
|
+
|
|
238
268
|
### When to Use RBTree
|
|
239
269
|
|
|
240
270
|
✅ **Use RBTree when you need:**
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -77,6 +77,7 @@ class RBTree
|
|
|
77
77
|
#
|
|
78
78
|
# @param args [Hash, Array, nil] optional initial data
|
|
79
79
|
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
80
|
+
# @param node_allocator [NodeAllocator] allocator instance to use (default: AutoShrinkNodePool.new)
|
|
80
81
|
# @yieldreturn [Object] optional initial data
|
|
81
82
|
# - If a Hash is provided, each key-value pair is inserted into the tree
|
|
82
83
|
# - If an Array is provided, it should contain [key, value] pairs
|
|
@@ -91,7 +92,7 @@ class RBTree
|
|
|
91
92
|
# tree = RBTree.new([[1, 'one'], [2, 'two']])
|
|
92
93
|
# @example Create with overwrite: false
|
|
93
94
|
# tree = RBTree.new([[1, 'one'], [1, 'uno']], overwrite: false)
|
|
94
|
-
def initialize(*args, overwrite: true, &block)
|
|
95
|
+
def initialize(*args, overwrite: true, node_allocator: AutoShrinkNodePool.new, &block)
|
|
95
96
|
@nil_node = Node.new
|
|
96
97
|
@nil_node.color = Node::BLACK
|
|
97
98
|
@nil_node.left = @nil_node
|
|
@@ -99,14 +100,21 @@ class RBTree
|
|
|
99
100
|
@root = @nil_node
|
|
100
101
|
@min_node = @nil_node
|
|
101
102
|
@hash_index = {} # Hash index for O(1) key lookup
|
|
102
|
-
@
|
|
103
|
+
@node_allocator = node_allocator
|
|
103
104
|
@key_count = 0
|
|
104
105
|
|
|
106
|
+
@overwrite = overwrite
|
|
107
|
+
|
|
105
108
|
if args.size > 0 || block_given?
|
|
106
109
|
insert(*args, overwrite: overwrite, &block)
|
|
107
110
|
end
|
|
108
111
|
end
|
|
109
112
|
|
|
113
|
+
# Returns a Hash containing all key-value pairs from the tree.
|
|
114
|
+
#
|
|
115
|
+
# @return [Hash] a new Hash with the tree's contents
|
|
116
|
+
def to_h = @hash_index.transform_values(&:value)
|
|
117
|
+
|
|
110
118
|
# Checks if the tree is empty.
|
|
111
119
|
#
|
|
112
120
|
# @return [Boolean] true if the tree contains no elements, false otherwise
|
|
@@ -298,7 +306,7 @@ class RBTree
|
|
|
298
306
|
# tree.insert({1 => 'one', 2 => 'two'})
|
|
299
307
|
# @example Bulk insert from Array
|
|
300
308
|
# tree.insert([[1, 'one'], [2, 'two']])
|
|
301
|
-
def insert(*args, overwrite:
|
|
309
|
+
def insert(*args, overwrite: @overwrite, &block)
|
|
302
310
|
if args.size == 2
|
|
303
311
|
key, value = args
|
|
304
312
|
insert_entry(key, value, overwrite: overwrite)
|
|
@@ -335,6 +343,19 @@ class RBTree
|
|
|
335
343
|
end
|
|
336
344
|
end
|
|
337
345
|
alias :[]= :insert
|
|
346
|
+
|
|
347
|
+
# Merges the contents of another tree, hash, or enumerable into this tree.
|
|
348
|
+
#
|
|
349
|
+
# @param other [RBTree, Hash, Enumerable] the source to merge from
|
|
350
|
+
# @param overwrite [Boolean] whether to overwrite existing keys (default: true)
|
|
351
|
+
# @return [RBTree] self
|
|
352
|
+
def merge!(other, overwrite: true)
|
|
353
|
+
if defined?(MultiRBTree) && other.is_a?(MultiRBTree)
|
|
354
|
+
raise ArgumentError, "Cannot merge MultiRBTree into RBTree"
|
|
355
|
+
end
|
|
356
|
+
insert(other, overwrite: overwrite)
|
|
357
|
+
self
|
|
358
|
+
end
|
|
338
359
|
|
|
339
360
|
# Deletes the key-value pair with the specified key.
|
|
340
361
|
#
|
|
@@ -1308,30 +1329,17 @@ class RBTree
|
|
|
1308
1329
|
# Allocates a new node or recycles one from the pool.
|
|
1309
1330
|
# @return [Node]
|
|
1310
1331
|
def allocate_node(key, value, color, left, right, parent)
|
|
1311
|
-
node = @
|
|
1312
|
-
if node
|
|
1313
|
-
node.key = key
|
|
1314
|
-
node.value = value
|
|
1315
|
-
node.color = color
|
|
1316
|
-
node.left = left
|
|
1317
|
-
node.right = right
|
|
1318
|
-
node.parent = parent
|
|
1319
|
-
node
|
|
1320
|
-
else
|
|
1321
|
-
node = Node.new(key, value, color, left, right, parent)
|
|
1322
|
-
end
|
|
1332
|
+
node = @node_allocator.allocate(key, value, color, left, right, parent)
|
|
1323
1333
|
@key_count += 1
|
|
1324
1334
|
node
|
|
1325
1335
|
end
|
|
1326
1336
|
|
|
1337
|
+
# Releases a node back to the pool.
|
|
1338
|
+
# @param node [Node] the node to release
|
|
1327
1339
|
# Releases a node back to the pool.
|
|
1328
1340
|
# @param node [Node] the node to release
|
|
1329
1341
|
def release_node(node)
|
|
1330
|
-
node
|
|
1331
|
-
node.right = nil
|
|
1332
|
-
node.parent = nil
|
|
1333
|
-
node.value = nil # Help GC
|
|
1334
|
-
@node_pool << node
|
|
1342
|
+
@node_allocator.release(node)
|
|
1335
1343
|
@key_count -= 1
|
|
1336
1344
|
end
|
|
1337
1345
|
|
|
@@ -1548,61 +1556,15 @@ class MultiRBTree < RBTree
|
|
|
1548
1556
|
# tree.succ(4) # => [5, "five"]
|
|
1549
1557
|
def succ(key, last: false) = (pair = super(key)) && [pair[0], pair[1].send(last ? :last : :first)]
|
|
1550
1558
|
|
|
1551
|
-
#
|
|
1559
|
+
# Merges the contents of another tree, hash, or enumerable into this tree.
|
|
1552
1560
|
#
|
|
1553
|
-
#
|
|
1554
|
-
# If the key doesn't exist, a new node is created.
|
|
1561
|
+
# Appends values from the other source to the existing values for each key.
|
|
1555
1562
|
#
|
|
1556
|
-
# @param
|
|
1557
|
-
# @
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
# tree = MultiRBTree.new
|
|
1562
|
-
# tree.insert(1, 'first')
|
|
1563
|
-
# tree.insert(1, 'second') # adds another value for key 1
|
|
1564
|
-
def insert_entry(key, value, **)
|
|
1565
|
-
if (node = @hash_index[key])
|
|
1566
|
-
node.value << value
|
|
1567
|
-
@value_count += 1
|
|
1568
|
-
return true
|
|
1569
|
-
end
|
|
1570
|
-
y = @nil_node
|
|
1571
|
-
x = @root
|
|
1572
|
-
while x != @nil_node
|
|
1573
|
-
y = x
|
|
1574
|
-
cmp = key <=> x.key
|
|
1575
|
-
if cmp == 0
|
|
1576
|
-
x.value << value
|
|
1577
|
-
@value_count += 1
|
|
1578
|
-
return true
|
|
1579
|
-
elsif cmp < 0
|
|
1580
|
-
x = x.left
|
|
1581
|
-
else
|
|
1582
|
-
x = x.right
|
|
1583
|
-
end
|
|
1584
|
-
end
|
|
1585
|
-
z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
|
|
1586
|
-
z.parent = y
|
|
1587
|
-
if y == @nil_node
|
|
1588
|
-
@root = z
|
|
1589
|
-
elsif (key <=> y.key) < 0
|
|
1590
|
-
y.left = z
|
|
1591
|
-
else
|
|
1592
|
-
y.right = z
|
|
1593
|
-
end
|
|
1594
|
-
z.left = @nil_node
|
|
1595
|
-
z.right = @nil_node
|
|
1596
|
-
z.color = Node::RED
|
|
1597
|
-
insert_fixup(z)
|
|
1598
|
-
@value_count += 1
|
|
1599
|
-
|
|
1600
|
-
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
1601
|
-
@min_node = z
|
|
1602
|
-
end
|
|
1603
|
-
|
|
1604
|
-
@hash_index[key] = z # Add to hash index
|
|
1605
|
-
true
|
|
1563
|
+
# @param other [RBTree, Hash, Enumerable] the source to merge from
|
|
1564
|
+
# @return [MultiRBTree] self
|
|
1565
|
+
def merge!(other)
|
|
1566
|
+
insert(other)
|
|
1567
|
+
self
|
|
1606
1568
|
end
|
|
1607
1569
|
|
|
1608
1570
|
# Deletes a single value for the specified key.
|
|
@@ -1692,6 +1654,63 @@ class MultiRBTree < RBTree
|
|
|
1692
1654
|
# @!visibility private
|
|
1693
1655
|
private
|
|
1694
1656
|
|
|
1657
|
+
# Inserts a value for the given key.
|
|
1658
|
+
#
|
|
1659
|
+
# If the key already exists, the value is appended to its list.
|
|
1660
|
+
# If the key doesn't exist, a new node is created.
|
|
1661
|
+
#
|
|
1662
|
+
# @param key [Object] the key (must implement <=>)
|
|
1663
|
+
# @param value [Object] the value to insert
|
|
1664
|
+
# @param overwrite [Boolean] ignored for MultiRBTree which always appends
|
|
1665
|
+
# @return [Boolean] always returns true
|
|
1666
|
+
# @example
|
|
1667
|
+
# tree = MultiRBTree.new
|
|
1668
|
+
# tree.insert(1, 'first')
|
|
1669
|
+
# tree.insert(1, 'second') # adds another value for key 1
|
|
1670
|
+
def insert_entry(key, value, **)
|
|
1671
|
+
if (node = @hash_index[key])
|
|
1672
|
+
node.value << value
|
|
1673
|
+
@value_count += 1
|
|
1674
|
+
return true
|
|
1675
|
+
end
|
|
1676
|
+
y = @nil_node
|
|
1677
|
+
x = @root
|
|
1678
|
+
while x != @nil_node
|
|
1679
|
+
y = x
|
|
1680
|
+
cmp = key <=> x.key
|
|
1681
|
+
if cmp == 0
|
|
1682
|
+
x.value << value
|
|
1683
|
+
@value_count += 1
|
|
1684
|
+
return true
|
|
1685
|
+
elsif cmp < 0
|
|
1686
|
+
x = x.left
|
|
1687
|
+
else
|
|
1688
|
+
x = x.right
|
|
1689
|
+
end
|
|
1690
|
+
end
|
|
1691
|
+
z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
|
|
1692
|
+
z.parent = y
|
|
1693
|
+
if y == @nil_node
|
|
1694
|
+
@root = z
|
|
1695
|
+
elsif (key <=> y.key) < 0
|
|
1696
|
+
y.left = z
|
|
1697
|
+
else
|
|
1698
|
+
y.right = z
|
|
1699
|
+
end
|
|
1700
|
+
z.left = @nil_node
|
|
1701
|
+
z.right = @nil_node
|
|
1702
|
+
z.color = Node::RED
|
|
1703
|
+
insert_fixup(z)
|
|
1704
|
+
@value_count += 1
|
|
1705
|
+
|
|
1706
|
+
if @min_node == @nil_node || (key <=> @min_node.key) < 0
|
|
1707
|
+
@min_node = z
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
@hash_index[key] = z # Add to hash index
|
|
1711
|
+
true
|
|
1712
|
+
end
|
|
1713
|
+
|
|
1695
1714
|
# Traverses the tree in ascending order, yielding each key-value pair.
|
|
1696
1715
|
#
|
|
1697
1716
|
# @param range [Range] the range of keys to traverse
|
|
@@ -1761,3 +1780,203 @@ class RBTree::Node
|
|
|
1761
1780
|
# @return [Array(Object, Object)] the key-value pair
|
|
1762
1781
|
def pair = [key, value]
|
|
1763
1782
|
end
|
|
1783
|
+
|
|
1784
|
+
# Allocator for RBTree nodes.
|
|
1785
|
+
#
|
|
1786
|
+
# @api private
|
|
1787
|
+
class RBTree::NodeAllocator
|
|
1788
|
+
# Allocates a new node.
|
|
1789
|
+
#
|
|
1790
|
+
# @param key [Object] the key
|
|
1791
|
+
# @param value [Object] the value
|
|
1792
|
+
# @param color [Boolean] the color (true=red, false=black)
|
|
1793
|
+
# @param left [Node] the left child
|
|
1794
|
+
# @param right [Node] the right child
|
|
1795
|
+
# @param parent [Node] the parent node
|
|
1796
|
+
def allocate(key, value, color, left, right, parent) = RBTree::Node.new(key, value, color, left, right, parent)
|
|
1797
|
+
|
|
1798
|
+
# Releases a node.
|
|
1799
|
+
#
|
|
1800
|
+
# @param node [Node] the node to release
|
|
1801
|
+
def release(node) = nil
|
|
1802
|
+
end
|
|
1803
|
+
|
|
1804
|
+
# Internal node pool for RBTree.
|
|
1805
|
+
#
|
|
1806
|
+
# Manages recycling of Node objects to reduce object allocation overhead.
|
|
1807
|
+
#
|
|
1808
|
+
# @api private
|
|
1809
|
+
class RBTree::NodePool < RBTree::NodeAllocator
|
|
1810
|
+
def initialize
|
|
1811
|
+
@pool = []
|
|
1812
|
+
end
|
|
1813
|
+
|
|
1814
|
+
# Allocates a new node or recycles one from the pool.
|
|
1815
|
+
#
|
|
1816
|
+
# @param key [Object] the key
|
|
1817
|
+
# @param value [Object] the value
|
|
1818
|
+
# @param color [Boolean] the color (true=red, false=black)
|
|
1819
|
+
# @param left [Node] the left child
|
|
1820
|
+
# @param right [Node] the right child
|
|
1821
|
+
# @param parent [Node] the parent node
|
|
1822
|
+
def allocate(key, value, color, left, right, parent)
|
|
1823
|
+
node = @pool.pop
|
|
1824
|
+
if node
|
|
1825
|
+
node.key = key
|
|
1826
|
+
node.value = value
|
|
1827
|
+
node.color = color
|
|
1828
|
+
node.left = left
|
|
1829
|
+
node.right = right
|
|
1830
|
+
node.parent = parent
|
|
1831
|
+
node
|
|
1832
|
+
else
|
|
1833
|
+
super
|
|
1834
|
+
end
|
|
1835
|
+
end
|
|
1836
|
+
|
|
1837
|
+
# Releases a node back to the pool.
|
|
1838
|
+
#
|
|
1839
|
+
# @param node [Node] the node to release
|
|
1840
|
+
def release(node)
|
|
1841
|
+
node.left = node.right = node.parent = node.value = node.key = nil
|
|
1842
|
+
@pool << node
|
|
1843
|
+
end
|
|
1844
|
+
end
|
|
1845
|
+
|
|
1846
|
+
# Internal node pool for RBTree.
|
|
1847
|
+
#
|
|
1848
|
+
# Manages recycling of Node objects to reduce object allocation overhead.
|
|
1849
|
+
# Includes an auto-shrink mechanism to release memory back to GC when
|
|
1850
|
+
# the pool size exceeds the fluctuation range of recent active node count.
|
|
1851
|
+
#
|
|
1852
|
+
# This class can be used to customize the node allocation strategy by passing
|
|
1853
|
+
# an instance to {RBTree#initialize}.
|
|
1854
|
+
class RBTree::AutoShrinkNodePool < RBTree::NodePool
|
|
1855
|
+
# Initializes a new AutoShrinkNodePool.
|
|
1856
|
+
#
|
|
1857
|
+
# @param max_maintenance_interval [Integer] maximum interval between maintenance checks (default: 1000)
|
|
1858
|
+
# @param target_check_interval [Float] target interval in seconds for maintenance checks (default: 1.0)
|
|
1859
|
+
# @param history_size [Integer] duration in seconds to keep history for fluctuation analysis (default: 120)
|
|
1860
|
+
# @param buffer_factor [Float] buffer factor to apply to observed fluctuation (default: 1.25)
|
|
1861
|
+
# @param reserve_ratio [Float] minimum reserve capacity as a ratio of max active nodes (default: 0.1)
|
|
1862
|
+
def initialize(
|
|
1863
|
+
max_maintenance_interval: 1000,
|
|
1864
|
+
target_check_interval: 1.0,
|
|
1865
|
+
history_size: 120,
|
|
1866
|
+
buffer_factor: 1.25,
|
|
1867
|
+
reserve_ratio: 0.1)
|
|
1868
|
+
@pool = []
|
|
1869
|
+
|
|
1870
|
+
@max_maintenance_interval = max_maintenance_interval
|
|
1871
|
+
@target_check_interval = target_check_interval
|
|
1872
|
+
@history_limit = history_size
|
|
1873
|
+
@buffer_ratio = buffer_factor
|
|
1874
|
+
@reserve_ratio = reserve_ratio
|
|
1875
|
+
|
|
1876
|
+
@maintenance_count = 0
|
|
1877
|
+
@check_interval = 1000
|
|
1878
|
+
@check_count = 0
|
|
1879
|
+
@avg_release_rate = nil
|
|
1880
|
+
@last_check_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1881
|
+
|
|
1882
|
+
@active_nodes = 0
|
|
1883
|
+
@global_max_active = 0
|
|
1884
|
+
@global_min_active = 0
|
|
1885
|
+
@max_active_in_interval = 0
|
|
1886
|
+
@min_active_in_interval = 0
|
|
1887
|
+
@history = []
|
|
1888
|
+
@current_target_capacity = Float::INFINITY
|
|
1889
|
+
end
|
|
1890
|
+
|
|
1891
|
+
# Allocates a new node or recycles one from the pool.
|
|
1892
|
+
#
|
|
1893
|
+
# @param key [Object] the key
|
|
1894
|
+
# @param value [Object] the value
|
|
1895
|
+
# @param color [Boolean] the color (true=red, false=black)
|
|
1896
|
+
# @param left [Node] the left child
|
|
1897
|
+
# @param right [Node] the right child
|
|
1898
|
+
# @param parent [Node] the parent node
|
|
1899
|
+
def allocate(key, value, color, left, right, parent)
|
|
1900
|
+
@active_nodes += 1
|
|
1901
|
+
@max_active_in_interval = @active_nodes if @active_nodes > @max_active_in_interval
|
|
1902
|
+
super
|
|
1903
|
+
end
|
|
1904
|
+
|
|
1905
|
+
# Releases a node back to the pool.
|
|
1906
|
+
#
|
|
1907
|
+
# Checks auto-shrink logic to decide whether to keep the node or let it be GC'd.
|
|
1908
|
+
#
|
|
1909
|
+
# @param node [Node] the node to release
|
|
1910
|
+
def release(node)
|
|
1911
|
+
@active_nodes -= 1
|
|
1912
|
+
@min_active_in_interval = @active_nodes if @active_nodes < @min_active_in_interval
|
|
1913
|
+
|
|
1914
|
+
@check_count += 1
|
|
1915
|
+
|
|
1916
|
+
perform_maintenance if @check_count >= @check_interval
|
|
1917
|
+
|
|
1918
|
+
super if @pool.size < @current_target_capacity
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
private
|
|
1922
|
+
|
|
1923
|
+
def perform_maintenance
|
|
1924
|
+
@maintenance_count += 1
|
|
1925
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1926
|
+
elapsed = now - @last_check_time
|
|
1927
|
+
return if elapsed <= 0
|
|
1928
|
+
|
|
1929
|
+
current_rate = @check_count / elapsed
|
|
1930
|
+
|
|
1931
|
+
if @avg_release_rate.nil?
|
|
1932
|
+
@avg_release_rate = current_rate
|
|
1933
|
+
else
|
|
1934
|
+
@avg_release_rate = (@avg_release_rate * 3 + current_rate) / 4
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1937
|
+
@check_interval = [[(@avg_release_rate * @target_check_interval).to_i, 1].max, @max_maintenance_interval].min
|
|
1938
|
+
|
|
1939
|
+
expired_min = false
|
|
1940
|
+
expired_max = false
|
|
1941
|
+
needs_recalc = false
|
|
1942
|
+
|
|
1943
|
+
cutoff_time = now - @history_limit
|
|
1944
|
+
while !@history.empty? && @history.first[0] < cutoff_time
|
|
1945
|
+
if !expired_min && @history.first[1] == @global_min_active
|
|
1946
|
+
expired_min = true
|
|
1947
|
+
needs_recalc = true
|
|
1948
|
+
end
|
|
1949
|
+
if !expired_max && @history.first[2] == @global_max_active
|
|
1950
|
+
expired_max = true
|
|
1951
|
+
needs_recalc = true
|
|
1952
|
+
end
|
|
1953
|
+
@history.shift
|
|
1954
|
+
end
|
|
1955
|
+
|
|
1956
|
+
@history << [now, @min_active_in_interval, @max_active_in_interval]
|
|
1957
|
+
|
|
1958
|
+
if @min_active_in_interval < @global_min_active
|
|
1959
|
+
@global_min_active = @min_active_in_interval
|
|
1960
|
+
expired_min = false
|
|
1961
|
+
needs_recalc = true
|
|
1962
|
+
end
|
|
1963
|
+
if @max_active_in_interval > @global_max_active
|
|
1964
|
+
@global_max_active = @max_active_in_interval
|
|
1965
|
+
expired_max = false
|
|
1966
|
+
needs_recalc = true
|
|
1967
|
+
end
|
|
1968
|
+
|
|
1969
|
+
@global_min_active = @history.map { |_, min, _| min }.min if expired_min
|
|
1970
|
+
@global_max_active = @history.map { |_, _, max| max }.max if expired_max
|
|
1971
|
+
if needs_recalc
|
|
1972
|
+
fluctuation = @global_max_active - @global_min_active
|
|
1973
|
+
reserve = (@reserve_ratio * @global_max_active).to_i
|
|
1974
|
+
@current_target_capacity = [(fluctuation * @buffer_ratio).to_i, reserve].max
|
|
1975
|
+
end
|
|
1976
|
+
|
|
1977
|
+
@check_count = 0
|
|
1978
|
+
@last_check_time = now
|
|
1979
|
+
@max_active_in_interval = @active_nodes
|
|
1980
|
+
@min_active_in_interval = @active_nodes
|
|
1981
|
+
end
|
|
1982
|
+
end
|
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
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Masahito Suzuki
|
|
@@ -25,7 +25,6 @@ files:
|
|
|
25
25
|
- README.ja.md
|
|
26
26
|
- README.md
|
|
27
27
|
- Rakefile
|
|
28
|
-
- lib/rbtree.old.rb
|
|
29
28
|
- lib/rbtree.rb
|
|
30
29
|
- lib/rbtree/version.rb
|
|
31
30
|
- rbtree-ruby.gemspec
|