rbtree-ruby 0.1.8 → 0.2.1

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: ad4ff4e15079d2034a111e0180f1abbf5f433283de5c125f45569f9b69f558fc
4
- data.tar.gz: 7355e1ae3964e79aa8d110c52e38672641c8d67ac1cb4cfbbd974fe6298f07f7
3
+ metadata.gz: 89348dfde7177409ecdca6a4184ebee7f83f3a57eebb84b3f4925ad4460a7a92
4
+ data.tar.gz: bd46b055e05a121787b67ecf20836181800abe0a230800703872c1412826ccfa
5
5
  SHA512:
6
- metadata.gz: e62ddc40bfc16d4afaf9430b679b03cfb50e89d3a02d7ab43a190b168015d4c6605baf579cc55abc0d42db207465538f99e81618beca873a1b4c654d1ab0c72f
7
- data.tar.gz: d11f0258d1ea5ea2686cc8bb581eb3b5c524554bf2182d68d54759e592d58d57e463bfa87b13dbabb67e68becfa8648108b267751791a1877e49bc7c6e2f6a06
6
+ metadata.gz: 908a2d3d58bfac4c7d092e745ebe7f3f2c391551ea5fa7822871d1d0334c64985169df7433342e0e77cad9cf85f8a6b1a265ee427d96445095b3aa27da73d8ef
7
+ data.tar.gz: c7b5afe4a5189deee48c6abc5cbca6340c14ebfc65d581f2788117bfb84aabca7757582ab5b6bbcf7cc5866e6350be4f85f87fda78ea49af8c394dd0884de6b3
data/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ 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.1] - 2026-01-14
9
+
10
+ ### Added
11
+ - **Enhanced iteration**: `each` now accepts `reverse:` and `safe:` options
12
+ - `tree.each(reverse: true) { ... }` (same as `reverse_each`)
13
+ - `tree.each(safe: true) { ... }` (modification-safe)
14
+ - `tree.each(reverse: true, safe: true) { ... }` (reverse modification-safe)
15
+ - In `MultiRBTree`, `reverse: true` also iterates over values in reverse insertion order.
16
+ - **Range query safe mode**: `safe:` option for `lt`, `lte`, `gt`, `gte`, `between`
17
+ - `tree.lt(10, safe: true) { |k, _| tree.delete(k) if k.even? }`
18
+ - Works with `:reverse` option: `tree.gt(5, reverse: true, safe: true)`
19
+ - Returns Enumerator: `tree.between(1, 100, safe: true).select { ... }`
20
+
21
+ ### Changed
22
+ - **MultiRBTree#get_all**: Now returns an `Enumerator` instead of an `Array` when no block is given.
23
+ - Improves consistency with other iteration methods.
24
+ - Safe handling for non-existent keys (returns empty Enumerator/nil logic handled safely).
25
+ - Use `.to_a` to get an Array: `tree.get_all(key).to_a`
26
+
27
+ ## [0.2.0] - 2026-01-14
28
+
29
+ ### Added
30
+ - **MultiRBTree**: `nearest(key, last: false)` option to choose first or last value
31
+
8
32
  ## [0.1.8] - 2026-01-14
9
33
 
10
34
  ### Changed
@@ -116,6 +140,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
116
140
  - ASCII diagrams for tree rotation operations
117
141
  - MIT License (Copyright © 2026 Masahito Suzuki)
118
142
 
143
+ [0.2.0]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.2.0
119
144
  [0.1.8]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.8
120
145
  [0.1.7]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.7
121
146
  [0.1.6]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.6
data/README.ja.md ADDED
@@ -0,0 +1,282 @@
1
+ # rbtree-ruby
2
+
3
+ 🌍 *[English](README.md) | [日本語](README.ja.md)*
4
+
5
+ Red-Black Tree(赤黒木)データ構造のピュアRuby実装です。挿入、削除、検索操作がO(log n)の時間計算量で実行できる、
6
+ 効率的な順序付きキーバリューストレージを提供します。
7
+
8
+ ## 特徴
9
+
10
+ - **自己平衡二分探索木**: 赤黒木の性質により最適なパフォーマンスを維持
11
+ - **順序付き操作**: 効率的な範囲クエリ、最小/最大値の取得、ソート済みイテレーション
12
+ - **複数値サポート**: `MultiRBTree`クラスで同一キーに複数の値を格納可能
13
+ - **ピュアRuby**: C拡張不要、あらゆるRuby実装で動作
14
+ - **充実したドキュメント**: 使用例付きの包括的なRDocドキュメント
15
+
16
+ ## インストール
17
+
18
+ Gemfileに以下を追加:
19
+
20
+ ```ruby
21
+ gem 'rbtree-ruby'
22
+ ```
23
+
24
+ 実行:
25
+
26
+ ```bash
27
+ bundle install
28
+ ```
29
+
30
+ または直接インストール:
31
+
32
+ ```bash
33
+ gem install rbtree-ruby
34
+ ```
35
+
36
+ ## 使い方
37
+
38
+ ### 基本的なRBTree
39
+
40
+ ```ruby
41
+ require 'rbtree'
42
+
43
+ # 空のツリーを作成
44
+ tree = RBTree.new
45
+
46
+ # データで初期化
47
+ tree = RBTree.new({3 => 'three', 1 => 'one', 2 => 'two'})
48
+ tree = RBTree[[5, 'five'], [4, 'four']]
49
+
50
+ # 値の挿入と取得
51
+ tree.insert(10, 'ten')
52
+ tree[20] = 'twenty'
53
+ puts tree[10] # => "ten"
54
+
55
+ # ソート順でイテレーション
56
+ tree.each { |key, value| puts "#{key}: #{value}" }
57
+ # 出力:
58
+ # 1: one
59
+ # 2: two
60
+ # 3: three
61
+ # 10: ten
62
+ # 20: twenty
63
+
64
+ # イテレーション中の変更
65
+ # 標準のHashやArrayとは異なり、`safe: true`オプションを指定することで
66
+ # イテレーション中に安全にキーの削除や挿入を行うことができます。
67
+ tree.each(safe: true) { |k, v| tree.delete(k) if k.even? }
68
+ tree.each(reverse: true) { |k, v| puts k } # reverse_eachと同じ
69
+
70
+ # 最小値と最大値
71
+ tree.min # => [1, "one"]
72
+ tree.max # => [20, "twenty"]
73
+
74
+ # 範囲クエリ(Enumeratorを返す、配列には.to_aを使用)
75
+ tree.lt(10).to_a # => [[1, "one"], [2, "two"], [3, "three"]]
76
+ tree.gte(10).to_a # => [[10, "ten"], [20, "twenty"]]
77
+ tree.between(2, 10).to_a # => [[2, "two"], [3, "three"], [10, "ten"]]
78
+
79
+ # shiftとpop
80
+ tree.shift # => [1, "one"] (最小値を削除)
81
+ tree.pop # => [20, "twenty"] (最大値を削除)
82
+
83
+ # 削除
84
+ tree.delete(3) # => "three"
85
+
86
+ # キーの存在確認
87
+ tree.has_key?(2) # => true
88
+ tree.size # => 2
89
+ ```
90
+
91
+ ### MultiRBTree(重複キー対応)
92
+
93
+ ```ruby
94
+ require 'rbtree'
95
+
96
+ tree = MultiRBTree.new
97
+
98
+ # 同じキーに複数の値を挿入
99
+ tree.insert(1, 'first one')
100
+ tree.insert(1, 'second one')
101
+ tree.insert(1, 'third one')
102
+ tree.insert(2, 'two')
103
+
104
+ tree.size # => 4 (キーバリューペアの総数)
105
+
106
+ # 最初の値を取得
107
+ tree.get(1) # => "first one"
108
+ tree[1] # => "first one"
109
+
110
+ # キーの全ての値を取得(Enumeratorを返す)
111
+ tree.get_all(1).to_a # => ["first one", "second one", "third one"]
112
+
113
+ # 全キーバリューペアをイテレーション
114
+ tree.each { |k, v| puts "#{k}: #{v}" }
115
+ # 出力:
116
+ # 1: first one
117
+ # 1: second one
118
+ # 1: third one
119
+ # 2: two
120
+
121
+ # 最初の値のみ削除
122
+ tree.delete_one(1) # => "first one"
123
+ tree.get(1) # => "second one"
124
+
125
+ # キーの全ての値を削除
126
+ tree.delete(1) # 残りの値を全て削除
127
+ ```
128
+
129
+ ### 最近傍キー検索
130
+
131
+ ```ruby
132
+ tree = RBTree.new({1 => 'one', 5 => 'five', 10 => 'ten'})
133
+
134
+ tree.nearest(4) # => [5, "five"] (4に最も近いキー)
135
+ tree.nearest(7) # => [5, "five"] (同距離の場合は小さいキー)
136
+ tree.nearest(8) # => [10, "ten"]
137
+ ```
138
+
139
+ ### 前後キー検索
140
+
141
+ ```ruby
142
+ tree = RBTree.new({1 => 'one', 3 => 'three', 5 => 'five', 7 => 'seven'})
143
+
144
+ tree.prev(5) # => [3, "three"] (5より小さい最大のキー)
145
+ tree.succ(5) # => [7, "seven"] (5より大きい最小のキー)
146
+
147
+ # キーが存在しなくても動作
148
+ tree.prev(4) # => [3, "three"] (4は存在しない、4未満の最大キーを返す)
149
+ tree.succ(4) # => [5, "five"] (4は存在しない、4より大きい最小キーを返す)
150
+
151
+ # 境界ではnilを返す
152
+ tree.prev(1) # => nil (1より小さいキーなし)
153
+ tree.succ(7) # => nil (7より大きいキーなし)
154
+ ```
155
+
156
+ ### 逆順範囲クエリ
157
+
158
+ 範囲クエリは`Enumerator`を返し(配列には`.to_a`を使用)、`:reverse`オプションをサポート:
159
+
160
+ ```ruby
161
+ tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
162
+
163
+ tree.lt(3).to_a # => [[1, "one"], [2, "two"]]
164
+ tree.lt(3, reverse: true).to_a # => [[2, "two"], [1, "one"]]
165
+ tree.lt(3).first # => [1, "one"] (遅延評価、配列は作成されない)
166
+
167
+ # 遅延評価
168
+ tree.gt(0).lazy.take(2).to_a # => [[1, "one"], [2, "two"]] (最初の2件のみ計算)
169
+ ```
170
+
171
+ ### MultiRBTree 値配列アクセス
172
+
173
+ 複数の値を持つキーで、どの値にアクセスするか選択:
174
+
175
+ ```ruby
176
+ tree = MultiRBTree.new
177
+ tree.insert(1, 'first')
178
+ tree.insert(1, 'second')
179
+ tree.insert(1, 'third')
180
+
181
+ # 最初または最後の値にアクセス
182
+ tree.get(1) # => "first"
183
+ tree.get(1, last: true) # => "third"
184
+ tree.get_first(1) # => "first"
185
+ tree.get_last(1) # => "third"
186
+
187
+ # どちらの端からも削除可能
188
+ tree.delete_first(1) # => "first"
189
+ tree.delete_last(1) # => "third"
190
+ tree.get(1) # => "second"
191
+
192
+ # min/maxの:lastオプション
193
+ tree.insert(2, 'a')
194
+ tree.insert(2, 'b')
195
+ tree.min # => [1, "second"] (最小キーの最初の値)
196
+ tree.max(last: true) # => [2, "b"] (最大キーの最後の値)
197
+ ```
198
+
199
+ ## パフォーマンス
200
+
201
+ 主要な操作は**O(log n)**時間で実行:
202
+
203
+ - `insert(key, value)` - O(log n)
204
+ - `delete(key)` - O(log n)
205
+ - `get(key)` / `[]` - **O(1)** (ハイブリッドハッシュインデックス)
206
+ - `has_key?` - **O(1)** (ハイブリッドハッシュインデックス)
207
+ - `min` - **O(1)**
208
+ - `max` - O(log n)
209
+ - `shift` / `pop` - O(log n)
210
+ - `prev` / `succ` - O(log n)、O(1)ハッシュチェック付き
211
+
212
+ 全要素のイテレーションはO(n)時間。
213
+
214
+ ### メモリ効率
215
+
216
+ RBTreeは内部的な**メモリプール**を使用してノードオブジェクトを再利用:
217
+ - 頻繁な挿入・削除時のGC負荷を大幅に削減
218
+ - 100,000回の循環操作ベンチマークで**GC時間0.0秒**を達成
219
+
220
+ ### RBTree vs Hash vs Array
221
+
222
+ 順序付き操作と空間的操作において、RBTreeは単に速いだけでなく、全く異なるクラスの性能を発揮。**50万件**でのベンチマーク:
223
+
224
+ | 操作 | RBTree | Hash/Array | 高速化 | 理由 |
225
+ |-----|--------|------------|-------|-----|
226
+ | **最近傍検索** | **O(log n)** | O(n) スキャン | **〜8,600倍高速** | 二分探索 vs 全件スキャン |
227
+ | **範囲クエリ** | **O(log n + k)** | O(n) フィルター | **〜540倍高速** | 部分木へ直接ジャンプ vs 全件スキャン |
228
+ | **最小値抽出** | **O(log n)** | O(n) 検索 | **〜160倍高速** | 連続的なリバランス vs 全件スキャン |
229
+ | **ソート済みイテレーション** | **O(n)** | O(n log n) | **無料** | 常にソート済み vs 明示的な`sort` |
230
+ | **キー検索** | **O(1)** | O(1) | **同等** | ハイブリッドハッシュインデックス |
231
+
232
+ ### RBTreeを使うべき場面
233
+
234
+ ✅ **RBTreeが適している場合:**
235
+ - キー順でのイテレーション
236
+ - 高速なmin/max取得
237
+ - 範囲クエリ(`between`, `lt`, `gt`, `lte`, `gte`)
238
+ - 最近傍キー検索
239
+ - 優先度キュー的な動作(キー順でshift/pop)
240
+
241
+ ✅ **Hashが適している場合:**
242
+ - 高速なキーバリュー検索のみ(RBTreeも同等に高速!)
243
+ - 順序付けが不要
244
+
245
+ ## APIドキュメント
246
+
247
+ 完全なRDocドキュメントを生成:
248
+
249
+ ```bash
250
+ rdoc lib/rbtree.rb
251
+ ```
252
+
253
+ `doc/index.html`をブラウザで開いてください。
254
+
255
+ ## 開発
256
+
257
+ リポジトリをチェックアウト後、`bundle install`で依存関係をインストール:
258
+
259
+ ```bash
260
+ # RDocドキュメント生成
261
+ rake rdoc
262
+
263
+ # gemのビルド
264
+ rake build
265
+
266
+ # ローカルインストール
267
+ rake install
268
+ ```
269
+
270
+ ## コントリビューション
271
+
272
+ バグ報告やプルリクエストはGitHubで受け付けています: https://github.com/firelzrd/rbtree-ruby
273
+
274
+ ## ライセンス
275
+
276
+ [MIT License](https://opensource.org/licenses/MIT)でオープンソースとして公開。
277
+
278
+ ## 作者
279
+
280
+ Masahito Suzuki (firelzrd@gmail.com)
281
+
282
+ Copyright © 2026 Masahito Suzuki
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # rbtree-ruby
2
2
 
3
- 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.
3
+ 🌍 *[English](README.md) | [日本語](README.ja.md)*
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.
4
7
 
5
8
  ## Features
6
9
 
@@ -58,14 +61,20 @@ tree.each { |key, value| puts "#{key}: #{value}" }
58
61
  # 10: ten
59
62
  # 20: twenty
60
63
 
64
+ # Modification during iteration
65
+ # Unlike standard Ruby Hash/Array, modification during iteration is fully supported
66
+ # with the `safe: true` option. This allows you to delete or insert keys safely while iterating.
67
+ tree.each(safe: true) { |k, v| tree.delete(k) if k.even? }
68
+ tree.each(reverse: true) { |k, v| puts k } # Same as reverse_each
69
+
61
70
  # Min and max
62
71
  tree.min # => [1, "one"]
63
72
  tree.max # => [20, "twenty"]
64
73
 
65
- # Range queries
66
- tree.lt(10) # => [[1, "one"], [2, "two"], [3, "three"]]
67
- tree.gte(10) # => [[10, "ten"], [20, "twenty"]]
68
- tree.between(2, 10) # => [[2, "two"], [3, "three"], [10, "ten"]]
74
+ # Range queries (return Enumerator, use .to_a for Array)
75
+ tree.lt(10).to_a # => [[1, "one"], [2, "two"], [3, "three"]]
76
+ tree.gte(10).to_a # => [[10, "ten"], [20, "twenty"]]
77
+ tree.between(2, 10).to_a # => [[2, "two"], [3, "three"], [10, "ten"]]
69
78
 
70
79
  # Shift and pop
71
80
  tree.shift # => [1, "one"] (removes minimum)
@@ -98,8 +107,8 @@ tree.size # => 4 (total number of key-value pairs)
98
107
  tree.get(1) # => "first one"
99
108
  tree[1] # => "first one"
100
109
 
101
- # Get all values for a key
102
- tree.get_all(1) # => ["first one", "second one", "third one"]
110
+ # Get all values for a key (returns Enumerator)
111
+ tree.get_all(1).to_a # => ["first one", "second one", "third one"]
103
112
 
104
113
  # Iterate over all key-value pairs
105
114
  tree.each { |k, v| puts "#{k}: #{v}" }
@@ -148,15 +157,17 @@ tree.succ(7) # => nil (no key larger than 7)
148
157
 
149
158
  ### Reverse Range Queries
150
159
 
151
- All range queries support a `:reverse` option to iterate in descending order:
160
+ All range queries return an `Enumerator` (use `.to_a` for Array) and support a `:reverse` option:
152
161
 
153
162
  ```ruby
154
163
  tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
155
164
 
156
- tree.lt(3) # => [[1, "one"], [2, "two"]]
157
- tree.lt(3, reverse: true) # => [[2, "two"], [1, "one"]]
165
+ tree.lt(3).to_a # => [[1, "one"], [2, "two"]]
166
+ tree.lt(3, reverse: true).to_a # => [[2, "two"], [1, "one"]]
167
+ tree.lt(3).first # => [1, "one"] (lazy, no array created)
158
168
 
159
- tree.between(1, 4, reverse: true) # => [[4, "four"], [3, "three"], [2, "two"], [1, "one"]]
169
+ # Lazy evaluation
170
+ tree.gt(0).lazy.take(2).to_a # => [[1, "one"], [2, "two"]] (only computes first 2)
160
171
  ```
161
172
 
162
173
  ### MultiRBTree Value Array Access
@@ -2,5 +2,5 @@
2
2
 
3
3
  class RBTree
4
4
  # The version of the rbtree-ruby gem
5
- VERSION = "0.1.8"
5
+ VERSION = "0.2.1"
6
6
  end
data/lib/rbtree.rb CHANGED
@@ -268,13 +268,15 @@ class RBTree
268
268
  def pop
269
269
  n = rightmost(@root)
270
270
  return nil if n == @nil_node
271
- result = [n.key, n.value]
271
+ result = n.pair
272
272
  delete(n.key)
273
273
  result
274
274
  end
275
275
 
276
- # Iterates over all key-value pairs in ascending order of keys.
276
+ # Iterates over all key-value pairs in ascending (or descending) order.
277
277
  #
278
+ # @param reverse [Boolean] if true, iterate in descending order (default: false)
279
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
278
280
  # @yield [key, value] each key-value pair in the tree
279
281
  # @return [Enumerator, RBTree] an Enumerator if no block is given, self otherwise
280
282
  # @example
@@ -284,9 +286,40 @@ class RBTree
284
286
  # # 1: one
285
287
  # # 2: two
286
288
  # # 3: three
287
- def each(&block)
288
- return enum_for(:each) unless block_given?
289
- traverse_asc(@root, &block)
289
+ #
290
+ # # Reverse iteration
291
+ # tree.each(reverse: true) { |k, v| ... }
292
+ #
293
+ # # Safe iteration for modifications
294
+ # tree.each(safe: true) do |k, v|
295
+ # tree.delete(k) if k.even?
296
+ # end
297
+ def each(reverse: false, safe: false, &block)
298
+ return enum_for(:each, reverse: reverse, safe: safe) unless block_given?
299
+ if safe
300
+ if reverse
301
+ pair = find_max
302
+ while pair
303
+ current_key = pair[0]
304
+ yield pair[0], pair[1]
305
+ pair = find_predecessor(current_key)
306
+ end
307
+ else
308
+ pair = find_min
309
+ while pair
310
+ current_key = pair[0]
311
+ yield pair[0], pair[1]
312
+ pair = find_successor(current_key)
313
+ end
314
+ end
315
+ else
316
+ if reverse
317
+ traverse_desc(@root, &block)
318
+ else
319
+ traverse_asc(@root, &block)
320
+ end
321
+ end
322
+ self
290
323
  end
291
324
 
292
325
  # Iterates over all key-value pairs in descending order of keys.
@@ -300,10 +333,15 @@ class RBTree
300
333
  # # 3: three
301
334
  # # 2: two
302
335
  # # 1: one
303
- def reverse_each(&block)
304
- return enum_for(:reverse_each) unless block_given?
305
- traverse_desc(@root, &block)
306
- end
336
+ # Iterates over all key-value pairs in descending order of keys.
337
+ #
338
+ # This is an alias for `each(reverse: true)`.
339
+ #
340
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
341
+ # @yield [key, value] each key-value pair in the tree
342
+ # @return [Enumerator, RBTree] an Enumerator if no block is given, self otherwise
343
+ # @see #each
344
+ def reverse_each(safe: false, &block) = each(reverse: true, safe: safe, &block)
307
345
 
308
346
  # Returns the minimum key-value pair without removing it.
309
347
  #
@@ -311,9 +349,7 @@ class RBTree
311
349
  # @example
312
350
  # tree = RBTree.new({3 => 'three', 1 => 'one', 2 => 'two'})
313
351
  # tree.min # => [1, "one"]
314
- def min
315
- @min_node == @nil_node ? nil : [@min_node.key, @min_node.value]
316
- end
352
+ def min = find_min
317
353
 
318
354
  # Returns the maximum key-value pair without removing it.
319
355
  #
@@ -321,25 +357,39 @@ class RBTree
321
357
  # @example
322
358
  # tree = RBTree.new({3 => 'three', 1 => 'one', 2 => 'two'})
323
359
  # tree.max # => [3, "three"]
324
- def max
325
- n = rightmost(@root)
326
- n == @nil_node ? nil : [n.key, n.value]
327
- end
360
+ def max = find_max
328
361
 
329
362
  # Retrieves all key-value pairs with keys less than the specified key.
330
363
  #
331
364
  # @param key [Object] the upper bound (exclusive)
332
365
  # @param reverse [Boolean] if true, iterate in descending order (default: false)
366
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
333
367
  # @yield [key, value] each matching key-value pair (if block given)
334
368
  # @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
335
369
  # @example
336
370
  # tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
337
371
  # tree.lt(3).to_a # => [[1, "one"], [2, "two"]]
338
372
  # tree.lt(3, reverse: true).first # => [2, "two"]
339
- # tree.lt(3) { |k, v| puts k } # prints keys, returns self
340
- def lt(key, reverse: false, &block)
341
- return enum_for(:lt, key, reverse: reverse) unless block_given?
342
- if reverse
373
+ # tree.lt(3, safe: true) { |k, _| tree.delete(k) if k.even? } # safe to delete
374
+ def lt(key, reverse: false, safe: false, &block)
375
+ return enum_for(:lt, key, reverse: reverse, safe: safe) unless block_given?
376
+ if safe
377
+ if reverse
378
+ pair = find_predecessor(key)
379
+ while pair
380
+ current_key = pair[0]
381
+ yield pair
382
+ pair = find_predecessor(current_key)
383
+ end
384
+ else
385
+ pair = find_min
386
+ while pair && pair[0] < key
387
+ current_key = pair[0]
388
+ yield pair
389
+ pair = find_successor(current_key)
390
+ end
391
+ end
392
+ elsif reverse
343
393
  traverse_lt_desc(@root, key, &block)
344
394
  else
345
395
  traverse_lt(@root, key, &block)
@@ -351,15 +401,32 @@ class RBTree
351
401
  #
352
402
  # @param key [Object] the upper bound (inclusive)
353
403
  # @param reverse [Boolean] if true, iterate in descending order (default: false)
404
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
354
405
  # @yield [key, value] each matching key-value pair (if block given)
355
406
  # @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
356
407
  # @example
357
408
  # tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
358
409
  # tree.lte(3).to_a # => [[1, "one"], [2, "two"], [3, "three"]]
359
410
  # tree.lte(3, reverse: true).first # => [3, "three"]
360
- def lte(key, reverse: false, &block)
361
- return enum_for(:lte, key, reverse: reverse) unless block_given?
362
- if reverse
411
+ def lte(key, reverse: false, safe: false, &block)
412
+ return enum_for(:lte, key, reverse: reverse, safe: safe) unless block_given?
413
+ if safe
414
+ if reverse
415
+ pair = @hash_index[key]&.pair || find_predecessor(key)
416
+ while pair
417
+ current_key = pair[0]
418
+ yield pair
419
+ pair = find_predecessor(current_key)
420
+ end
421
+ else
422
+ pair = find_min
423
+ while pair && pair[0] <= key
424
+ current_key = pair[0]
425
+ yield pair
426
+ pair = succ(current_key)
427
+ end
428
+ end
429
+ elsif reverse
363
430
  traverse_lte_desc(@root, key, &block)
364
431
  else
365
432
  traverse_lte(@root, key, &block)
@@ -371,15 +438,32 @@ class RBTree
371
438
  #
372
439
  # @param key [Object] the lower bound (exclusive)
373
440
  # @param reverse [Boolean] if true, iterate in descending order (default: false)
441
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
374
442
  # @yield [key, value] each matching key-value pair (if block given)
375
443
  # @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
376
444
  # @example
377
445
  # tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
378
446
  # tree.gt(2).to_a # => [[3, "three"], [4, "four"]]
379
447
  # tree.gt(2, reverse: true).first # => [4, "four"]
380
- def gt(key, reverse: false, &block)
381
- return enum_for(:gt, key, reverse: reverse) unless block_given?
382
- if reverse
448
+ def gt(key, reverse: false, safe: false, &block)
449
+ return enum_for(:gt, key, reverse: reverse, safe: safe) unless block_given?
450
+ if safe
451
+ if reverse
452
+ pair = find_max
453
+ while pair && pair[0] > key
454
+ current_key = pair[0]
455
+ yield pair
456
+ pair = find_predecessor(current_key)
457
+ end
458
+ else
459
+ pair = find_successor(key)
460
+ while pair
461
+ current_key = pair[0]
462
+ yield pair
463
+ pair = find_successor(current_key)
464
+ end
465
+ end
466
+ elsif reverse
383
467
  traverse_gt_desc(@root, key, &block)
384
468
  else
385
469
  traverse_gt(@root, key, &block)
@@ -391,15 +475,32 @@ class RBTree
391
475
  #
392
476
  # @param key [Object] the lower bound (inclusive)
393
477
  # @param reverse [Boolean] if true, iterate in descending order (default: false)
478
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
394
479
  # @yield [key, value] each matching key-value pair (if block given)
395
480
  # @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
396
481
  # @example
397
482
  # tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four'})
398
483
  # tree.gte(2).to_a # => [[2, "two"], [3, "three"], [4, "four"]]
399
484
  # tree.gte(2, reverse: true).first # => [4, "four"]
400
- def gte(key, reverse: false, &block)
401
- return enum_for(:gte, key, reverse: reverse) unless block_given?
402
- if reverse
485
+ def gte(key, reverse: false, safe: false, &block)
486
+ return enum_for(:gte, key, reverse: reverse, safe: safe) unless block_given?
487
+ if safe
488
+ if reverse
489
+ pair = find_max
490
+ while pair && pair[0] >= key
491
+ current_key = pair[0]
492
+ yield pair
493
+ pair = find_predecessor(current_key)
494
+ end
495
+ else
496
+ pair = @hash_index[key]&.pair || find_successor(key)
497
+ while pair
498
+ current_key = pair[0]
499
+ yield pair
500
+ pair = find_successor(current_key)
501
+ end
502
+ end
503
+ elsif reverse
403
504
  traverse_gte_desc(@root, key, &block)
404
505
  else
405
506
  traverse_gte(@root, key, &block)
@@ -414,15 +515,34 @@ class RBTree
414
515
  # @param include_min [Boolean] whether to include the lower bound (default: true)
415
516
  # @param include_max [Boolean] whether to include the upper bound (default: true)
416
517
  # @param reverse [Boolean] if true, iterate in descending order (default: false)
518
+ # @param safe [Boolean] if true, safe for modifications during iteration (default: false)
417
519
  # @yield [key, value] each matching key-value pair (if block given)
418
520
  # @return [Enumerator, RBTree] Enumerator if no block given, self otherwise
419
521
  # @example
420
522
  # tree = RBTree.new({1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five'})
421
523
  # tree.between(2, 4).to_a # => [[2, "two"], [3, "three"], [4, "four"]]
422
524
  # tree.between(2, 4, reverse: true).first # => [4, "four"]
423
- def between(min, max, include_min: true, include_max: true, reverse: false, &block)
424
- return enum_for(:between, min, max, include_min: include_min, include_max: include_max, reverse: reverse) unless block_given?
425
- if reverse
525
+ def between(min, max, include_min: true, include_max: true, reverse: false, safe: false, &block)
526
+ return enum_for(:between, min, max, include_min: include_min, include_max: include_max, reverse: reverse, safe: safe) unless block_given?
527
+ if safe
528
+ if reverse
529
+ pair = include_max && @hash_index[max]&.pair || find_predecessor(max)
530
+ while pair && pair[0] > min
531
+ current_key = pair[0]
532
+ yield pair
533
+ pair = find_predecessor(current_key)
534
+ end
535
+ yield pair if pair && include_min && pair[0] == min
536
+ else
537
+ pair = include_min && @hash_index[min]&.pair || find_successor(min)
538
+ while pair && pair[0] < max
539
+ current_key = pair[0]
540
+ yield pair
541
+ pair = find_successor(current_key)
542
+ end
543
+ yield pair if pair && include_max && pair[0] == max
544
+ end
545
+ elsif reverse
426
546
  traverse_between_desc(@root, min, max, include_min, include_max, &block)
427
547
  else
428
548
  traverse_between(@root, min, max, include_min, include_max, &block)
@@ -445,7 +565,7 @@ class RBTree
445
565
  def nearest(key)
446
566
  return nil unless key.respond_to?(:-)
447
567
  n = find_nearest_node(key)
448
- n == @nil_node ? nil : [n.key, n.value]
568
+ n == @nil_node ? nil : n.pair
449
569
  end
450
570
 
451
571
  # Returns the key-value pair with the largest key that is smaller than the given key.
@@ -462,7 +582,7 @@ class RBTree
462
582
  # tree.prev(1) # => nil (no predecessor)
463
583
  def prev(key)
464
584
  n = find_predecessor_node(key)
465
- n == @nil_node ? nil : [n.key, n.value]
585
+ n == @nil_node ? nil : n.pair
466
586
  end
467
587
 
468
588
  # Returns the key-value pair with the smallest key that is larger than the given key.
@@ -479,7 +599,7 @@ class RBTree
479
599
  # tree.succ(7) # => nil (no successor)
480
600
  def succ(key)
481
601
  n = find_successor_node(key)
482
- n == @nil_node ? nil : [n.key, n.value]
602
+ n == @nil_node ? nil : n.pair
483
603
  end
484
604
 
485
605
  # Validates the red-black tree properties.
@@ -515,7 +635,7 @@ class RBTree
515
635
  current = current.left
516
636
  end
517
637
  current = stack.pop
518
- yield current.key, current.value
638
+ yield current.pair
519
639
  current = current.right
520
640
  end
521
641
  end
@@ -534,7 +654,7 @@ class RBTree
534
654
  current = current.right
535
655
  end
536
656
  current = stack.pop
537
- yield current.key, current.value
657
+ yield current.pair
538
658
  current = current.left
539
659
  end
540
660
  end
@@ -550,7 +670,7 @@ class RBTree
550
670
 
551
671
  traverse_lt(node.left, key, &block)
552
672
  if (node.key <=> key) < 0
553
- yield node.key, node.value
673
+ yield node.pair
554
674
  traverse_lt(node.right, key, &block)
555
675
  end
556
676
  end
@@ -566,7 +686,7 @@ class RBTree
566
686
 
567
687
  traverse_lte(node.left, key, &block)
568
688
  if (node.key <=> key) <= 0
569
- yield node.key, node.value
689
+ yield node.pair
570
690
  traverse_lte(node.right, key, &block)
571
691
  end
572
692
  end
@@ -582,7 +702,7 @@ class RBTree
582
702
 
583
703
  if (node.key <=> key) > 0
584
704
  traverse_gt(node.left, key, &block)
585
- yield node.key, node.value
705
+ yield node.pair
586
706
  end
587
707
  traverse_gt(node.right, key, &block)
588
708
  end
@@ -598,7 +718,7 @@ class RBTree
598
718
 
599
719
  if (node.key <=> key) >= 0
600
720
  traverse_gte(node.left, key, &block)
601
- yield node.key, node.value
721
+ yield node.pair
602
722
  end
603
723
  traverse_gte(node.right, key, &block)
604
724
  end
@@ -622,7 +742,7 @@ class RBTree
622
742
  less = include_max ? (node.key <=> max) <= 0 : (node.key <=> max) < 0
623
743
 
624
744
  if greater && less
625
- yield node.key, node.value
745
+ yield node.pair
626
746
  end
627
747
 
628
748
  if (node.key <=> max) < 0
@@ -641,7 +761,7 @@ class RBTree
641
761
 
642
762
  if (node.key <=> key) < 0
643
763
  traverse_lt_desc(node.right, key, &block)
644
- yield node.key, node.value
764
+ yield node.pair
645
765
  end
646
766
  traverse_lt_desc(node.left, key, &block)
647
767
  end
@@ -657,7 +777,7 @@ class RBTree
657
777
 
658
778
  if (node.key <=> key) <= 0
659
779
  traverse_lte_desc(node.right, key, &block)
660
- yield node.key, node.value
780
+ yield node.pair
661
781
  end
662
782
  traverse_lte_desc(node.left, key, &block)
663
783
  end
@@ -673,7 +793,7 @@ class RBTree
673
793
 
674
794
  traverse_gt_desc(node.right, key, &block)
675
795
  if (node.key <=> key) > 0
676
- yield node.key, node.value
796
+ yield node.pair
677
797
  traverse_gt_desc(node.left, key, &block)
678
798
  end
679
799
  end
@@ -689,7 +809,7 @@ class RBTree
689
809
 
690
810
  traverse_gte_desc(node.right, key, &block)
691
811
  if (node.key <=> key) >= 0
692
- yield node.key, node.value
812
+ yield node.pair
693
813
  traverse_gte_desc(node.left, key, &block)
694
814
  end
695
815
  end
@@ -714,7 +834,7 @@ class RBTree
714
834
  less = include_max ? (node.key <=> max) <= 0 : (node.key <=> max) < 0
715
835
 
716
836
  if greater && less
717
- yield node.key, node.value
837
+ yield node.pair
718
838
  end
719
839
 
720
840
  if (node.key <=> min) > 0
@@ -1011,6 +1131,16 @@ class RBTree
1011
1131
  predecessor
1012
1132
  end
1013
1133
 
1134
+ # Finds the node with the largest key that is smaller than the given key.
1135
+ # Returns the key-value pair if found, or nil if the tree is empty.
1136
+ #
1137
+ # @param key [Object] the reference key
1138
+ # @return [Array(Object, Object), nil] the key-value pair, or nil if tree is empty
1139
+ def find_predecessor(key)
1140
+ n = find_predecessor_node(key)
1141
+ n == @nil_node ? nil : n.pair
1142
+ end
1143
+
1014
1144
  # Finds the node with the smallest key that is larger than the given key.
1015
1145
  #
1016
1146
  # If the key exists in the tree, returns its successor node.
@@ -1051,6 +1181,16 @@ class RBTree
1051
1181
  successor
1052
1182
  end
1053
1183
 
1184
+ # Finds the node with the smallest key that is larger than the given key.
1185
+ # Returns the key-value pair if found, or nil if the tree is empty.
1186
+ #
1187
+ # @param key [Object] the reference key
1188
+ # @return [Array(Object, Object), nil] the key-value pair, or nil if tree is empty
1189
+ def find_successor(key)
1190
+ n = find_successor_node(key)
1191
+ n == @nil_node ? nil : n.pair
1192
+ end
1193
+
1054
1194
  # Finds the leftmost (minimum) node in a subtree.
1055
1195
  #
1056
1196
  # @param node [Node] the root of the subtree
@@ -1062,6 +1202,11 @@ class RBTree
1062
1202
  node
1063
1203
  end
1064
1204
 
1205
+ # Returns the minimum key-value pair.
1206
+ #
1207
+ # @return [Array(Object, Object), nil] a two-element array [key, value], or nil if tree is empty
1208
+ def find_min = @min_node == @nil_node ? nil : @min_node.pair
1209
+
1065
1210
  # Finds the rightmost (maximum) node in a subtree.
1066
1211
  #
1067
1212
  # @param node [Node] the root of the subtree
@@ -1073,6 +1218,11 @@ class RBTree
1073
1218
  node
1074
1219
  end
1075
1220
 
1221
+ # Returns the maximum key-value pair.
1222
+ #
1223
+ # @return [Array(Object, Object), nil] a two-element array [key, value], or nil if tree is empty
1224
+ def find_max = (n = rightmost(@root)) == @nil_node ? nil : n.pair
1225
+
1076
1226
  # Performs a left rotation on the given node.
1077
1227
  #
1078
1228
  # Transforms the tree structure:
@@ -1304,8 +1454,8 @@ class MultiRBTree < RBTree
1304
1454
  # tree.insert(1, 'second')
1305
1455
  # tree.get_all(1).to_a # => ["first", "second"]
1306
1456
  def get_all(key)
1307
- n = find_node(key)
1308
- n == @nil_node || n.value.empty? ? nil : n.value
1457
+ return enum_for(:get_all, key) unless block_given?
1458
+ @hash_index[key]&.value&.each { |v| yield v }
1309
1459
  end
1310
1460
 
1311
1461
  # Inserts a value for the given key.
@@ -1461,23 +1611,81 @@ class MultiRBTree < RBTree
1461
1611
  [n.key, last ? n.value.last : n.value.first]
1462
1612
  end
1463
1613
 
1464
- def nearest(key)
1614
+ def nearest(key, last: false)
1465
1615
  n = find_nearest_node(key)
1466
- n == @nil_node || n.value.empty? ? nil : [n.key, n.value.first]
1467
- end
1468
-
1469
- def prev(key, last: false)
1470
- n = find_predecessor_node(key)
1471
1616
  return nil if n == @nil_node || n.value.empty?
1472
1617
  [n.key, last ? n.value.last : n.value.first]
1473
1618
  end
1474
1619
 
1475
- def succ(key, last: false)
1476
- n = find_successor_node(key)
1477
- return nil if n == @nil_node || n.value.empty?
1478
- [n.key, last ? n.value.last : n.value.first]
1620
+ def each(reverse: false, safe: false, &block)
1621
+ return enum_for(:each, reverse: reverse, safe: safe) unless block_given?
1622
+ if safe
1623
+ super do |k, vals|
1624
+ vals.send(reverse ? :reverse_each : :each) { |v| yield k, v }
1625
+ end
1626
+ else
1627
+ super
1628
+ end
1479
1629
  end
1480
1630
 
1631
+ def lt(key, reverse: false, safe: false, &block)
1632
+ return enum_for(:lt, key, reverse: reverse, safe: safe) unless block_given?
1633
+ if safe
1634
+ super do |k, vals|
1635
+ vals.send(reverse ? :reverse_each : :each) { |v| yield k, v }
1636
+ end
1637
+ else
1638
+ super
1639
+ end
1640
+ end
1641
+
1642
+ def lte(key, reverse: false, safe: false, &block)
1643
+ return enum_for(:lte, key, reverse: reverse, safe: safe) unless block_given?
1644
+ if safe
1645
+ super do |k, vals|
1646
+ vals.send(reverse ? :reverse_each : :each) { |v| yield k, v }
1647
+ end
1648
+ else
1649
+ super
1650
+ end
1651
+ end
1652
+
1653
+ def gt(key, reverse: false, safe: false, &block)
1654
+ return enum_for(:gt, key, reverse: reverse, safe: safe) unless block_given?
1655
+ if safe
1656
+ super do |k, vals|
1657
+ vals.send(reverse ? :reverse_each : :each) { |v| yield k, v }
1658
+ end
1659
+ else
1660
+ super
1661
+ end
1662
+ end
1663
+
1664
+ def gte(key, reverse: false, safe: false, &block)
1665
+ return enum_for(:gte, key, reverse: reverse, safe: safe) unless block_given?
1666
+ if safe
1667
+ super do |k, vals|
1668
+ vals.send(reverse ? :reverse_each : :each) { |v| yield k, v }
1669
+ end
1670
+ else
1671
+ super
1672
+ end
1673
+ end
1674
+
1675
+ def between(min, max, include_min: true, include_max: true, reverse: false, safe: false, &block)
1676
+ return enum_for(:between, min, max, include_min: include_min, include_max: include_max, reverse: reverse, safe: safe) unless block_given?
1677
+ if safe
1678
+ super do |k, vals|
1679
+ vals.send(reverse ? :reverse_each : :each) { |v| yield k, v }
1680
+ end
1681
+ else
1682
+ super
1683
+ end
1684
+ end
1685
+
1686
+ def prev(key, last: false) = (n = super) && [n.key, last ? n.value.last : n.value.first]
1687
+ def succ(key, last: false) = (n = super) && [n.key, last ? n.value.last : n.value.first]
1688
+
1481
1689
  private
1482
1690
 
1483
1691
  def traverse_asc(node, &block)
@@ -1677,4 +1885,8 @@ class RBTree::Node
1677
1885
  # Checks if the node is black.
1678
1886
  # @return [Boolean] true if black, false otherwise
1679
1887
  def black? = @color == BLACK
1888
+
1889
+ # Returns the key-value pair.
1890
+ # @return [Array(Object, Object)] the key-value pair
1891
+ def pair = [key, value]
1680
1892
  end
data/rbtree-ruby.gemspec CHANGED
@@ -8,11 +8,12 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Masahito Suzuki"]
9
9
  spec.email = ["firelzrd@gmail.com"]
10
10
 
11
- spec.summary = "A pure Ruby implementation of Red-Black Tree with multi-value support"
11
+ spec.summary = "A pure Ruby Red-Black Tree with O(1) lookup, range queries, and multi-value support"
12
12
  spec.description = <<~DESC
13
- RBTree is a pure Ruby implementation of the Red-Black Tree data structure,
14
- providing efficient ordered key-value storage with O(log n) operations.
15
- Includes MultiRBTree for handling duplicate keys with linked lists.
13
+ A high-performance pure Ruby Red-Black Tree implementation. Features: O(1) key
14
+ lookup via hybrid hash index, O(log n) insert/delete, lazy Enumerator-based
15
+ range queries (lt/gt/between), nearest/prev/succ search, memory-efficient node
16
+ pooling, and MultiRBTree for duplicate keys with first/last value access.
16
17
  DESC
17
18
  spec.homepage = "https://github.com/firelzrd/rbtree-ruby"
18
19
  spec.license = "MIT"
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.1.8
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahito Suzuki
@@ -10,9 +10,10 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: |
13
- RBTree is a pure Ruby implementation of the Red-Black Tree data structure,
14
- providing efficient ordered key-value storage with O(log n) operations.
15
- Includes MultiRBTree for handling duplicate keys with linked lists.
13
+ A high-performance pure Ruby Red-Black Tree implementation. Features: O(1) key
14
+ lookup via hybrid hash index, O(log n) insert/delete, lazy Enumerator-based
15
+ range queries (lt/gt/between), nearest/prev/succ search, memory-efficient node
16
+ pooling, and MultiRBTree for duplicate keys with first/last value access.
16
17
  email:
17
18
  - firelzrd@gmail.com
18
19
  executables: []
@@ -21,6 +22,7 @@ extra_rdoc_files: []
21
22
  files:
22
23
  - CHANGELOG.md
23
24
  - LICENSE
25
+ - README.ja.md
24
26
  - README.md
25
27
  - Rakefile
26
28
  - lib/rbtree.rb
@@ -49,5 +51,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
51
  requirements: []
50
52
  rubygems_version: 4.0.3
51
53
  specification_version: 4
52
- summary: A pure Ruby implementation of Red-Black Tree with multi-value support
54
+ summary: A pure Ruby Red-Black Tree with O(1) lookup, range queries, and multi-value
55
+ support
53
56
  test_files: []