red-black-tree 0.1.7 → 0.1.9
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.md +13 -10
- data/lib/red-black-tree.rb +149 -115
- data/lib/red_black_tree/node/left_right_element_referencers.rb +9 -2
- data/lib/red_black_tree/node.rb +1 -1
- data/lib/red_black_tree/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06ad66aa31594940e116b62cd872c61dc65ebb90964e097d84b4d540e9eefa2d
|
|
4
|
+
data.tar.gz: 74ae5eae6622755db6e1df89fe03e598e8c9c3bd22ce20ebe363464c60c6f7f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a17c8c5c6e2aab3cd53736d00ba3367b402450383b9f8a078669887d2590eed699e4d559dd588c18d90a0ce4fcde64b6a4c9aee8fc6452ab2d663e50c79b11bb
|
|
7
|
+
data.tar.gz: cfcd5d572e43089fce345cab92d8327c3002f6c282f422316dc16d48020672ac8a62f878b33a67d73f55027498df1f36fa5d1f7526f31a943b0860338a7f00c5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.9] - 2026-05-10
|
|
4
|
+
|
|
5
|
+
- Add `Enumerable` to `RedBlackTree`
|
|
6
|
+
- Fix `RedBlackTree#clear!` not detaching nodes
|
|
7
|
+
- Fix `LeafNodeComparable` being prepended to `LeafNode`
|
|
8
|
+
|
|
9
|
+
## [0.1.8] - 2025-07-26
|
|
10
|
+
|
|
11
|
+
- Refactor to slim down and de-duplicate some methods
|
|
12
|
+
|
|
3
13
|
## [0.1.7] - 2025-01-05
|
|
4
14
|
|
|
5
15
|
- Change `RedBlackTree#delete!` return value to the deleted node
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# RedBlackTree
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
3
6
|
[Red-black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) data structure for Ruby.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
@@ -83,20 +86,20 @@ Benchmark.ips do |x|
|
|
|
83
86
|
x.compare!
|
|
84
87
|
end
|
|
85
88
|
|
|
86
|
-
#=> ruby
|
|
89
|
+
#=> ruby 4.0.3 (2026-04-21 revision 85ddef263a) +YJIT +PRISM [arm64-darwin23]
|
|
87
90
|
#=> Warming up --------------------------------------
|
|
88
91
|
#=> RedBlackTree 1.000 i/100ms
|
|
89
92
|
#=> Array (gradual sort) 1.000 i/100ms
|
|
90
|
-
#=> Array (single sort)
|
|
93
|
+
#=> Array (single sort) 81.000 i/100ms
|
|
91
94
|
#=> Calculating -------------------------------------
|
|
92
|
-
#=> RedBlackTree
|
|
93
|
-
#=> Array (gradual sort) 0.
|
|
94
|
-
#=> Array (single sort)
|
|
95
|
-
#=>
|
|
95
|
+
#=> RedBlackTree 12.170 (± 8.2%) i/s (82.17 ms/i) - 61.000 in 5.023274s
|
|
96
|
+
#=> Array (gradual sort) 0.208 (± 0.0%) i/s (4.82 s/i) - 2.000 in 9.632733s
|
|
97
|
+
#=> Array (single sort) 815.762 (± 1.2%) i/s (1.23 ms/i) - 4.131k in 5.064788s
|
|
98
|
+
#=>
|
|
96
99
|
#=> Comparison:
|
|
97
|
-
#=> Array (single sort):
|
|
98
|
-
#=> RedBlackTree:
|
|
99
|
-
#=> Array (gradual sort): 0.
|
|
100
|
+
#=> Array (single sort): 815.8 i/s
|
|
101
|
+
#=> RedBlackTree: 12.2 i/s - 67.03x slower
|
|
102
|
+
#=> Array (gradual sort): 0.2 i/s - 3928.64x slower
|
|
100
103
|
```
|
|
101
104
|
|
|
102
105
|
### Sort and search 10,000 elements
|
|
@@ -119,7 +122,7 @@ Benchmark.ips do |x|
|
|
|
119
122
|
x.report("RedBlackTree#search") do
|
|
120
123
|
tree = RedBlackTree.new
|
|
121
124
|
sample_data.each { |work| tree << WorkNode.new(work); }
|
|
122
|
-
raise unless tree.search search_sample
|
|
125
|
+
raise unless tree.search { |node| node.data == search_sample }
|
|
123
126
|
end
|
|
124
127
|
|
|
125
128
|
# 1:1 comparison
|
data/lib/red-black-tree.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "red_black_tree/node/leaf_node"
|
|
|
6
6
|
|
|
7
7
|
class RedBlackTree
|
|
8
8
|
include Utils
|
|
9
|
+
include Enumerable
|
|
9
10
|
|
|
10
11
|
# @return [Integer] the number of valid/non-leaf nodes
|
|
11
12
|
attr_reader :size
|
|
@@ -106,27 +107,7 @@ class RedBlackTree
|
|
|
106
107
|
node.parent = target_parent
|
|
107
108
|
node.red!
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
if node.parent.sibling && node.parent.sibling.red?
|
|
111
|
-
node.parent.black!
|
|
112
|
-
node.parent.sibling.black!
|
|
113
|
-
node.parent.parent.red!
|
|
114
|
-
node = node.parent.parent
|
|
115
|
-
else
|
|
116
|
-
opp_direction = node.opposite_position
|
|
117
|
-
if node.parent.position == opp_direction
|
|
118
|
-
rotate_sub_tree! node.parent, opp_direction
|
|
119
|
-
node = node[opp_direction]
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
opp_direction = node.opposite_position
|
|
123
|
-
rotate_sub_tree! node.parent.parent, opp_direction
|
|
124
|
-
node.parent.black!
|
|
125
|
-
node.parent[opp_direction].red!
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
@root.black!
|
|
129
|
-
end
|
|
110
|
+
rebalance_after_insertion! node
|
|
130
111
|
end
|
|
131
112
|
|
|
132
113
|
node.validate! @root == node
|
|
@@ -153,71 +134,11 @@ class RedBlackTree
|
|
|
153
134
|
original_node = node
|
|
154
135
|
|
|
155
136
|
if node.children_are_valid?
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
successor = node.left
|
|
159
|
-
successor = successor.left until successor.left.leaf?
|
|
160
|
-
node.swap_colour_with! successor
|
|
161
|
-
node.swap_position_with! successor
|
|
162
|
-
node.swap_position_with! LeafNode.new
|
|
163
|
-
|
|
164
|
-
@root = successor if is_root
|
|
137
|
+
delete_node_with_two_children! node
|
|
165
138
|
elsif node.single_child_is_valid?
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
valid_child = node.children.find(&:valid?)
|
|
169
|
-
valid_child.black!
|
|
170
|
-
node.swap_position_with! valid_child
|
|
171
|
-
node.swap_position_with! LeafNode.new
|
|
172
|
-
|
|
173
|
-
@root = valid_child if is_root
|
|
139
|
+
delete_node_with_one_child! node
|
|
174
140
|
elsif node.children_are_leaves?
|
|
175
|
-
|
|
176
|
-
@root = nil
|
|
177
|
-
elsif node.red?
|
|
178
|
-
node.swap_position_with! LeafNode.new
|
|
179
|
-
else
|
|
180
|
-
loop do
|
|
181
|
-
if node.sibling && node.sibling.valid? && node.sibling.red?
|
|
182
|
-
node.parent.red!
|
|
183
|
-
node.sibling.black!
|
|
184
|
-
rotate_sub_tree! node.parent, node.position
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
|
|
188
|
-
node.sibling.red! unless node.sibling.leaf?
|
|
189
|
-
node.close_nephew.black!
|
|
190
|
-
rotate_sub_tree! node.sibling, node.opposite_position
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
|
|
194
|
-
case node.parent.colour
|
|
195
|
-
when Node::RED then node.sibling.red!
|
|
196
|
-
when Node::BLACK then node.sibling.black!
|
|
197
|
-
end
|
|
198
|
-
node.parent.black!
|
|
199
|
-
node.distant_nephew.black!
|
|
200
|
-
rotate_sub_tree! node.parent, node.position
|
|
201
|
-
|
|
202
|
-
break
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
if node.parent && node.parent.red?
|
|
206
|
-
node.sibling.red! unless node.sibling.leaf?
|
|
207
|
-
node.parent.black!
|
|
208
|
-
|
|
209
|
-
break
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
if node.sibling && !node.sibling.leaf?
|
|
213
|
-
node.sibling.red!
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
break unless node = node.parent
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
original_node.swap_position_with! LeafNode.new
|
|
220
|
-
end
|
|
141
|
+
delete_leaf_node! node, original_node
|
|
221
142
|
end
|
|
222
143
|
|
|
223
144
|
original_node.tree = nil
|
|
@@ -233,6 +154,11 @@ class RedBlackTree
|
|
|
233
154
|
#
|
|
234
155
|
# @return [RedBlackTree] self
|
|
235
156
|
def clear!
|
|
157
|
+
traverse_post_order do |node|
|
|
158
|
+
node.tree = nil
|
|
159
|
+
node.colour = node.parent = node.left = node.right = nil
|
|
160
|
+
end
|
|
161
|
+
|
|
236
162
|
@root = nil
|
|
237
163
|
@size = 0
|
|
238
164
|
@left_most_node = nil
|
|
@@ -259,7 +185,7 @@ class RedBlackTree
|
|
|
259
185
|
alias_method :find, :search
|
|
260
186
|
|
|
261
187
|
def select &block
|
|
262
|
-
raise ArgumentError, "block must be provided for select" unless
|
|
188
|
+
raise ArgumentError, "block must be provided for select" unless block_given?
|
|
263
189
|
|
|
264
190
|
_select_by_block block, @root
|
|
265
191
|
end
|
|
@@ -270,7 +196,18 @@ class RedBlackTree
|
|
|
270
196
|
#
|
|
271
197
|
# @return [true, false]
|
|
272
198
|
def include? data
|
|
273
|
-
|
|
199
|
+
!search(data).nil?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Traverses the tree in in-order and yields each node.
|
|
203
|
+
# When called without a block, returns an {Enumerator}.
|
|
204
|
+
#
|
|
205
|
+
# @yield [RedBlackTree::Node] the block to be executed for each node
|
|
206
|
+
# @return [void, Enumerator]
|
|
207
|
+
def each &block
|
|
208
|
+
return enum_for(:each) unless block_given?
|
|
209
|
+
|
|
210
|
+
traverse_in_order(&block)
|
|
274
211
|
end
|
|
275
212
|
|
|
276
213
|
# Traverses the tree in pre-order and yields each node.
|
|
@@ -323,16 +260,37 @@ class RedBlackTree
|
|
|
323
260
|
queue = [@root]
|
|
324
261
|
until queue.empty?
|
|
325
262
|
node = queue.shift
|
|
326
|
-
next if node.nil? || node.leaf?
|
|
327
|
-
|
|
328
263
|
block.call node
|
|
329
|
-
queue << node.left
|
|
330
|
-
queue << node.right
|
|
264
|
+
queue << node.left unless node.left.leaf?
|
|
265
|
+
queue << node.right unless node.right.leaf?
|
|
331
266
|
end
|
|
332
267
|
end
|
|
333
268
|
|
|
334
269
|
private
|
|
335
270
|
|
|
271
|
+
def is_root? node
|
|
272
|
+
node && @root && node.object_id == @root.object_id
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def increment_size!
|
|
276
|
+
@size += 1
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def decrement_size!
|
|
280
|
+
@size -= 1
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def update_left_most_node!
|
|
284
|
+
unless @root
|
|
285
|
+
@left_most_node = nil
|
|
286
|
+
return
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
current = @root
|
|
290
|
+
current = current.left until current.left.leaf?
|
|
291
|
+
@left_most_node = current
|
|
292
|
+
end
|
|
293
|
+
|
|
336
294
|
# Rotates a (sub-)tree starting from the given node in the given direction.
|
|
337
295
|
#
|
|
338
296
|
# @param node [RedBlackTree::Node] the root node of the sub-tree
|
|
@@ -359,12 +317,111 @@ class RedBlackTree
|
|
|
359
317
|
opp_direction_child
|
|
360
318
|
end
|
|
361
319
|
|
|
320
|
+
def rebalance_after_insertion! node
|
|
321
|
+
while node.parent && node.parent.red? do
|
|
322
|
+
if node.parent.sibling && node.parent.sibling.red?
|
|
323
|
+
node.parent.black!
|
|
324
|
+
node.parent.sibling.black!
|
|
325
|
+
node.parent.parent.red!
|
|
326
|
+
node = node.parent.parent
|
|
327
|
+
else
|
|
328
|
+
opp_direction = node.opposite_position
|
|
329
|
+
if node.parent.position == opp_direction
|
|
330
|
+
rotate_sub_tree! node.parent, opp_direction
|
|
331
|
+
node = node[opp_direction]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
opp_direction = node.opposite_position
|
|
335
|
+
rotate_sub_tree! node.parent.parent, opp_direction
|
|
336
|
+
node.parent.black!
|
|
337
|
+
node.parent[opp_direction].red!
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
@root.black!
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def delete_node_with_two_children! node
|
|
345
|
+
is_root = is_root? node
|
|
346
|
+
|
|
347
|
+
predecessor = node.left
|
|
348
|
+
predecessor = predecessor.right until predecessor.right.leaf?
|
|
349
|
+
node.swap_colour_with! predecessor
|
|
350
|
+
node.swap_position_with! predecessor
|
|
351
|
+
node.swap_position_with! LeafNode.new
|
|
352
|
+
|
|
353
|
+
@root = predecessor if is_root
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def delete_node_with_one_child! node
|
|
357
|
+
is_root = is_root? node
|
|
358
|
+
|
|
359
|
+
valid_child = node.children.find(&:valid?)
|
|
360
|
+
valid_child.black!
|
|
361
|
+
node.swap_position_with! valid_child
|
|
362
|
+
node.swap_position_with! LeafNode.new
|
|
363
|
+
|
|
364
|
+
@root = valid_child if is_root
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def delete_leaf_node! node, original_node
|
|
368
|
+
if is_root? node
|
|
369
|
+
@root = nil
|
|
370
|
+
elsif node.red?
|
|
371
|
+
node.swap_position_with! LeafNode.new
|
|
372
|
+
else
|
|
373
|
+
rebalance_after_deletion! node
|
|
374
|
+
original_node.swap_position_with! LeafNode.new
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def rebalance_after_deletion! node
|
|
379
|
+
loop do
|
|
380
|
+
if node.sibling && node.sibling.valid? && node.sibling.red?
|
|
381
|
+
node.parent.red!
|
|
382
|
+
node.sibling.black!
|
|
383
|
+
rotate_sub_tree! node.parent, node.position
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
|
|
387
|
+
node.sibling.red! unless node.sibling.leaf?
|
|
388
|
+
node.close_nephew.black!
|
|
389
|
+
rotate_sub_tree! node.sibling, node.opposite_position
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
|
|
393
|
+
case node.parent.colour
|
|
394
|
+
when Node::RED then node.sibling.red!
|
|
395
|
+
when Node::BLACK then node.sibling.black!
|
|
396
|
+
end
|
|
397
|
+
node.parent.black!
|
|
398
|
+
node.distant_nephew.black!
|
|
399
|
+
rotate_sub_tree! node.parent, node.position
|
|
400
|
+
|
|
401
|
+
break
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
if node.parent && node.parent.red?
|
|
405
|
+
node.sibling.red! unless node.sibling.leaf?
|
|
406
|
+
node.parent.black!
|
|
407
|
+
|
|
408
|
+
break
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
if node.sibling && !node.sibling.leaf?
|
|
412
|
+
node.sibling.red!
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
break unless node = node.parent
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
362
419
|
def _search_by_data data, node
|
|
363
420
|
return if node.nil? || node.leaf?
|
|
364
421
|
return node if data == node.data
|
|
365
422
|
|
|
366
|
-
|
|
367
|
-
if
|
|
423
|
+
comparison = data <=> node.data
|
|
424
|
+
if comparison && comparison >= 0
|
|
368
425
|
_search_by_data data, node.right
|
|
369
426
|
else
|
|
370
427
|
_search_by_data data, node.left
|
|
@@ -388,27 +445,4 @@ class RedBlackTree
|
|
|
388
445
|
end
|
|
389
446
|
end
|
|
390
447
|
end
|
|
391
|
-
|
|
392
|
-
def is_root? node
|
|
393
|
-
node && @root && node.object_id == @root.object_id
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
def increment_size!
|
|
397
|
-
@size += 1
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
def decrement_size!
|
|
401
|
-
@size -= 1
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
def update_left_most_node!
|
|
405
|
-
unless @root
|
|
406
|
-
@left_most_node = nil
|
|
407
|
-
return
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
current = @root
|
|
411
|
-
current = current.left until current.left.leaf?
|
|
412
|
-
@left_most_node = current
|
|
413
|
-
end
|
|
414
448
|
end
|
|
@@ -4,20 +4,27 @@ class RedBlackTree
|
|
|
4
4
|
class Node
|
|
5
5
|
module LeftRightElementReferencers # @private
|
|
6
6
|
def [] direction
|
|
7
|
+
validate_direction! direction
|
|
7
8
|
case direction
|
|
8
9
|
when Node::LEFT then @left
|
|
9
10
|
when Node::RIGHT then @right
|
|
10
|
-
else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}, got #{direction}"
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def []= direction, node
|
|
15
|
+
validate_direction! direction
|
|
15
16
|
case direction
|
|
16
17
|
when Node::LEFT then @left = node
|
|
17
18
|
when Node::RIGHT then @right = node
|
|
18
|
-
else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}, got #{direction}"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def validate_direction! direction
|
|
25
|
+
return if [Node::LEFT, Node::RIGHT].include?(direction)
|
|
26
|
+
raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}, got #{direction}"
|
|
27
|
+
end
|
|
21
28
|
end
|
|
22
29
|
end
|
|
23
30
|
end
|
data/lib/red_black_tree/node.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: red-black-tree
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Young
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
email:
|
|
13
13
|
- djry1999@gmail.com
|
|
@@ -49,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
49
49
|
- !ruby/object:Gem::Version
|
|
50
50
|
version: '0'
|
|
51
51
|
requirements: []
|
|
52
|
-
rubygems_version:
|
|
52
|
+
rubygems_version: 4.0.6
|
|
53
53
|
specification_version: 4
|
|
54
54
|
summary: Red-Black Tree Data Structure for Ruby
|
|
55
55
|
test_files: []
|