red-black-tree 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +155 -6
- data/lib/red-black-tree.rb +141 -40
- data/lib/red_black_tree/node/data_delegation.rb +17 -0
- data/lib/red_black_tree/node/leaf_node_comparable.rb +13 -0
- data/lib/red_black_tree/node/left_right_element_referencers.rb +2 -2
- data/lib/red_black_tree/node.rb +15 -6
- data/lib/red_black_tree/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b536af387a65d00a6d05596a8b209b32b500abf5cf156d5ab7169ab0149f6b2
|
4
|
+
data.tar.gz: 0bc6b9317ee4659228b201d5feb0c3146b356c5831ebe00a003d5d4f491020dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea60c092e82dcecf019933df7348db451e10922437d4ac90c325fb1654ad5226738513b4a4d2e2ee45f19d2af86753c7f5932584a66ec0e90bf8c3ea0ce4836a
|
7
|
+
data.tar.gz: c022d30679141753341dee36142b6690756d1797fe11e2dab34b4ddeeaafcc163693a3686bf4e2a324bd60951b869d9cd6d9496d49fed0c2a9fa4c7c3335751a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.3] - 2024-10-21
|
4
|
+
|
5
|
+
- Make `RedBlackTree#left_most_node` public
|
6
|
+
- Add `RedBlackTree#traverse_pre_order`
|
7
|
+
- Add `RedBlackTree#traverse_in_order`
|
8
|
+
- Add `RedBlackTree#traverse_post_order`
|
9
|
+
- Add `RedBlackTree#traverse_level_order`
|
10
|
+
- Add `RedBlackTree#traverse`, alias of `RedBlackTree#traverse_in_order`
|
11
|
+
- Extend `RedBlackTree#search` to accept a block
|
12
|
+
- Delegate missing `RedBlackTree::Node` methods to its `#data`
|
13
|
+
|
14
|
+
## [0.1.2] - 2024-09-08
|
15
|
+
|
16
|
+
- Fix a bunch of issues in `RedBlackTree#insert!` and `RedBlackTree#delete!` algorithms
|
17
|
+
- Fix `RedBlackTree::LeafNode`s being marked red
|
18
|
+
- Handle comparison with `RedBlackTree::LeafNode` in subclasses of `RedBlackTree::Node`
|
19
|
+
- Add `RedBlackTree#include?`
|
20
|
+
- Add `RedBlackTree#search`
|
21
|
+
- Alias `RedBlackTree#left_most_node` as `RedBlackTree#min`
|
22
|
+
|
3
23
|
## [0.1.1] - 2024-08-04
|
4
24
|
|
5
25
|
- Update `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` in README
|
data/README.md
CHANGED
@@ -17,20 +17,17 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
17
17
|
```ruby
|
18
18
|
Work = Struct.new :min_latency, keyword_init: true
|
19
19
|
|
20
|
+
# Needs to be implemented by you
|
20
21
|
class WorkNode < RedBlackTree::Node
|
21
22
|
def <=> other
|
22
|
-
|
23
|
-
1
|
24
|
-
else
|
25
|
-
self.data.min_latency <=> other.data.min_latency
|
26
|
-
end
|
23
|
+
self.data.min_latency <=> other.data.min_latency
|
27
24
|
end
|
28
25
|
end
|
29
26
|
|
30
27
|
tree = RedBlackTree.new
|
31
28
|
|
32
|
-
tree << WorkNode.new(Work.new min_latency: 1.2)
|
33
29
|
tree << WorkNode.new(Work.new min_latency: 0.8)
|
30
|
+
tree << WorkNode.new(Work.new min_latency: 1.2)
|
34
31
|
tree << WorkNode.new(Work.new min_latency: 0.8)
|
35
32
|
tree << WorkNode.new(Work.new min_latency: 0.4)
|
36
33
|
|
@@ -40,6 +37,158 @@ until tree.empty?
|
|
40
37
|
end
|
41
38
|
```
|
42
39
|
|
40
|
+
## Performance
|
41
|
+
|
42
|
+
> [!NOTE]
|
43
|
+
> Red-black trees are designed for specific use cases and are not intended as a general-purpose data structure. The
|
44
|
+
comparisons below are provided merely to illustrate the performance characteristics of the gem. However, it is important
|
45
|
+
to note that the benchmarks do not take into account the self-balancing nature of red-black trees.
|
46
|
+
|
47
|
+
### Sort and iterate 10,000 elements
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'benchmark/ips'
|
51
|
+
|
52
|
+
Work = Struct.new :min_latency, keyword_init: true
|
53
|
+
|
54
|
+
class WorkNode < RedBlackTree::Node
|
55
|
+
def <=> other
|
56
|
+
self.data.min_latency <=> other.data.min_latency
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
sample_data = 10_000.times.map { Work.new(min_latency: rand(1_000)) }
|
61
|
+
|
62
|
+
Benchmark.ips do |x|
|
63
|
+
x.report("RedBlackTree") do
|
64
|
+
tree = RedBlackTree.new
|
65
|
+
sample_data.each { |work| tree << WorkNode.new(work); }
|
66
|
+
tree.shift until tree.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
# 1:1 comparison
|
70
|
+
x.report("Array (gradual sort)") do
|
71
|
+
array = []
|
72
|
+
sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
|
73
|
+
array.shift until array.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
x.report("Array (single sort)") do
|
77
|
+
array = []
|
78
|
+
sample_data.each { |work| array << work; }
|
79
|
+
array.sort_by!(&:min_latency)
|
80
|
+
array.shift until array.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
x.compare!
|
84
|
+
end
|
85
|
+
|
86
|
+
#=> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
|
87
|
+
#=> Warming up --------------------------------------
|
88
|
+
#=> RedBlackTree 1.000 i/100ms
|
89
|
+
#=> Array (gradual sort) 1.000 i/100ms
|
90
|
+
#=> Array (single sort) 78.000 i/100ms
|
91
|
+
#=> Calculating -------------------------------------
|
92
|
+
#=> RedBlackTree 5.417 (± 0.0%) i/s (184.61 ms/i) - 28.000 in 5.172532s
|
93
|
+
#=> Array (gradual sort) 0.268 (± 0.0%) i/s (3.74 s/i) - 2.000 in 7.473005s
|
94
|
+
#=> Array (single sort) 768.691 (± 2.2%) i/s (1.30 ms/i) - 3.900k in 5.076337s
|
95
|
+
#=>
|
96
|
+
#=> Comparison:
|
97
|
+
#=> Array (single sort): 768.7 i/s
|
98
|
+
#=> RedBlackTree: 5.4 i/s - 141.91x slower
|
99
|
+
#=> Array (gradual sort): 0.3 i/s - 2872.03x slower
|
100
|
+
```
|
101
|
+
|
102
|
+
### Sort and search 10,000 elements
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'benchmark/ips'
|
106
|
+
|
107
|
+
Work = Struct.new :min_latency, keyword_init: true
|
108
|
+
|
109
|
+
class WorkNode < RedBlackTree::Node
|
110
|
+
def <=> other
|
111
|
+
self.data.min_latency <=> other.data.min_latency
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
sample_data = 10_000.times.map { Work.new(min_latency: rand(1_000)) }
|
116
|
+
search_sample = sample_data.sample
|
117
|
+
|
118
|
+
Benchmark.ips do |x|
|
119
|
+
x.report("RedBlackTree#search") do
|
120
|
+
tree = RedBlackTree.new
|
121
|
+
sample_data.each { |work| tree << WorkNode.new(work); }
|
122
|
+
raise unless tree.search search_sample
|
123
|
+
end
|
124
|
+
|
125
|
+
# 1:1 comparison
|
126
|
+
x.report("Array#find (gradual sort)") do
|
127
|
+
array = []
|
128
|
+
sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
|
129
|
+
raise unless array.find { |work| work.min_latency == search_sample.min_latency }
|
130
|
+
end
|
131
|
+
|
132
|
+
x.report("Array#find (single sort)") do
|
133
|
+
array = []
|
134
|
+
sample_data.each { |work| array << work; }
|
135
|
+
array.sort_by!(&:min_latency)
|
136
|
+
raise unless array.find { |work| work.min_latency == search_sample.min_latency }
|
137
|
+
end
|
138
|
+
|
139
|
+
# 1:1 comparison
|
140
|
+
x.report("Array#bsearch (gradual sort)") do
|
141
|
+
array = []
|
142
|
+
sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
|
143
|
+
raise unless array.bsearch { |work| search_sample.min_latency <= work.min_latency }
|
144
|
+
end
|
145
|
+
|
146
|
+
x.report("Array#bsearch (single sort)") do
|
147
|
+
array = []
|
148
|
+
sample_data.each { |work| array << work; }
|
149
|
+
array.sort_by!(&:min_latency)
|
150
|
+
raise unless array.bsearch { |work| search_sample.min_latency <= work.min_latency }
|
151
|
+
end
|
152
|
+
|
153
|
+
x.compare!
|
154
|
+
end
|
155
|
+
|
156
|
+
#=> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
|
157
|
+
#=> Warming up --------------------------------------
|
158
|
+
#=> RedBlackTree#search 1.000 i/100ms
|
159
|
+
#=> Array#find (gradual sort)
|
160
|
+
#=> 1.000 i/100ms
|
161
|
+
#=> Array#find (single sort)
|
162
|
+
#=> 69.000 i/100ms
|
163
|
+
#=> Array#bsearch (gradual sort)
|
164
|
+
#=> 1.000 i/100ms
|
165
|
+
#=> Array#bsearch (single sort)
|
166
|
+
#=> 89.000 i/100ms
|
167
|
+
#=> Calculating -------------------------------------
|
168
|
+
#=> RedBlackTree#search 12.926 (± 0.0%) i/s (77.36 ms/i) - 65.000 in 5.030736s
|
169
|
+
#=> Array#find (gradual sort)
|
170
|
+
#=> 0.262 (± 0.0%) i/s (3.81 s/i) - 2.000 in 7.623953s
|
171
|
+
#=> Array#find (single sort)
|
172
|
+
#=> 690.631 (± 1.0%) i/s (1.45 ms/i) - 3.519k in 5.095823s
|
173
|
+
#=> Array#bsearch (gradual sort)
|
174
|
+
#=> 0.267 (± 0.0%) i/s (3.75 s/i) - 2.000 in 7.492482s
|
175
|
+
#=> Array#bsearch (single sort)
|
176
|
+
#=> 895.413 (± 1.7%) i/s (1.12 ms/i) - 4.539k in 5.070590s
|
177
|
+
#=>
|
178
|
+
#=> Comparison:
|
179
|
+
#=> Array#bsearch (single sort): 895.4 i/s
|
180
|
+
#=> Array#find (single sort): 690.6 i/s - 1.30x slower
|
181
|
+
#=> RedBlackTree#search: 12.9 i/s - 69.27x slower
|
182
|
+
#=> Array#bsearch (gradual sort): 0.3 i/s - 3354.39x slower
|
183
|
+
#=> Array#find (gradual sort): 0.3 i/s - 3412.57x slower
|
184
|
+
```
|
185
|
+
|
186
|
+
## WIP Features
|
187
|
+
|
188
|
+
- `RedBlackTree#max`
|
189
|
+
- `RedBlackTree#height`
|
190
|
+
- `RedBlackTree#clear`
|
191
|
+
|
43
192
|
## Development
|
44
193
|
|
45
194
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the
|
data/lib/red-black-tree.rb
CHANGED
@@ -14,9 +14,9 @@ class RedBlackTree
|
|
14
14
|
# @return [RedBlackTree::Node, nil] the root node
|
15
15
|
attr_reader :root
|
16
16
|
|
17
|
-
# @private
|
18
17
|
# @return [RedBlackTree::Node, nil] the left most node
|
19
18
|
attr_reader :left_most_node
|
19
|
+
alias_method :min, :left_most_node
|
20
20
|
|
21
21
|
def initialize
|
22
22
|
@size = 0
|
@@ -41,8 +41,13 @@ class RedBlackTree
|
|
41
41
|
#
|
42
42
|
# @return [RedBlackTree::Node, nil] the removed node
|
43
43
|
def shift
|
44
|
-
|
45
|
-
|
44
|
+
return unless @left_most_node
|
45
|
+
|
46
|
+
node = @left_most_node.dup
|
47
|
+
node.colour = node.parent = node.left = node.right = nil
|
48
|
+
|
49
|
+
delete! @left_most_node
|
50
|
+
|
46
51
|
node
|
47
52
|
end
|
48
53
|
|
@@ -72,8 +77,8 @@ class RedBlackTree
|
|
72
77
|
#
|
73
78
|
# @private ideally this is only used internally e.g. in #<< which has context on the ideal location for the node
|
74
79
|
# @param node [RedBlackTree::Node] the node to be inserted
|
75
|
-
# @param target_parent [RedBlackTree::Node
|
76
|
-
# @param direction ["left", "right"
|
80
|
+
# @param target_parent [RedBlackTree::Node] the parent under which the node should be inserted
|
81
|
+
# @param direction ["left", "right"] the direction of the node relative to the parent
|
77
82
|
# @return [RedBlackTree] self
|
78
83
|
def insert! node, target_parent = nil, direction = nil
|
79
84
|
raise ArgumentError, "cannot insert leaf node" if node.instance_of? LeafNode
|
@@ -85,8 +90,6 @@ class RedBlackTree
|
|
85
90
|
raise ArgumentError, "Target parent already has #{direction} child" if (child = target_parent[direction]) && child.valid?
|
86
91
|
end
|
87
92
|
|
88
|
-
opp_direction = opposite_direction direction if direction
|
89
|
-
|
90
93
|
node.parent = nil
|
91
94
|
node.left = LeafNode.new
|
92
95
|
node.left.parent = node
|
@@ -108,17 +111,16 @@ class RedBlackTree
|
|
108
111
|
node.parent.parent.red!
|
109
112
|
node = node.parent.parent
|
110
113
|
else
|
111
|
-
|
112
|
-
|
113
|
-
rotate_sub_tree! node, opp_direction
|
114
|
+
opp_direction = node.opposite_position
|
115
|
+
if node.parent.position == opp_direction
|
116
|
+
rotate_sub_tree! node.parent, opp_direction
|
117
|
+
node = node[opp_direction]
|
114
118
|
end
|
115
119
|
|
120
|
+
opp_direction = node.opposite_position
|
121
|
+
rotate_sub_tree! node.parent.parent, opp_direction
|
116
122
|
node.parent.black!
|
117
|
-
|
118
|
-
if node.parent.parent
|
119
|
-
node.parent.parent.red!
|
120
|
-
rotate_sub_tree! node.parent.parent, direction
|
121
|
-
end
|
123
|
+
node.parent[opp_direction].red!
|
122
124
|
end
|
123
125
|
|
124
126
|
@root.black!
|
@@ -140,6 +142,8 @@ class RedBlackTree
|
|
140
142
|
def delete! node
|
141
143
|
raise ArgumentError, "cannot delete leaf node" if node.instance_of? LeafNode
|
142
144
|
|
145
|
+
original_node = node
|
146
|
+
|
143
147
|
if node.children_are_valid?
|
144
148
|
successor = node.left
|
145
149
|
successor = successor.left until successor.left.leaf?
|
@@ -160,19 +164,19 @@ class RedBlackTree
|
|
160
164
|
if is_root? node
|
161
165
|
@root = nil
|
162
166
|
elsif node.red?
|
163
|
-
|
164
|
-
node.swap_position_with! leaf
|
167
|
+
node.swap_position_with! LeafNode.new
|
165
168
|
else
|
166
|
-
direction = node.position
|
167
|
-
opp_direction = opposite_direction direction
|
168
|
-
|
169
169
|
loop do
|
170
|
-
if node.sibling.valid? && node.sibling.red?
|
170
|
+
if node.sibling && node.sibling.valid? && node.sibling.red?
|
171
171
|
node.parent.red!
|
172
172
|
node.sibling.black!
|
173
|
-
rotate_sub_tree! node.parent,
|
173
|
+
rotate_sub_tree! node.parent, node.position
|
174
|
+
end
|
174
175
|
|
175
|
-
|
176
|
+
if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
|
177
|
+
node.sibling.red! unless node.sibling.leaf?
|
178
|
+
node.close_nephew.black!
|
179
|
+
rotate_sub_tree! node.sibling, node.opposite_position
|
176
180
|
end
|
177
181
|
|
178
182
|
if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
|
@@ -182,35 +186,30 @@ class RedBlackTree
|
|
182
186
|
end
|
183
187
|
node.parent.black!
|
184
188
|
node.distant_nephew.black!
|
185
|
-
rotate_sub_tree! node.parent,
|
189
|
+
rotate_sub_tree! node.parent, node.position
|
186
190
|
|
187
191
|
break
|
188
192
|
end
|
189
193
|
|
190
|
-
if node.
|
191
|
-
node.sibling.red!
|
192
|
-
node.
|
193
|
-
rotate_sub_tree! node.sibling, opp_direction
|
194
|
+
if node.parent && node.parent.red?
|
195
|
+
node.sibling.red! unless node.sibling.leaf?
|
196
|
+
node.parent.black!
|
194
197
|
|
195
|
-
|
198
|
+
break
|
196
199
|
end
|
197
200
|
|
198
|
-
if node.
|
201
|
+
if node.sibling && !node.sibling.leaf?
|
199
202
|
node.sibling.red!
|
200
|
-
node.parent.black!
|
201
|
-
|
202
|
-
break
|
203
203
|
end
|
204
204
|
|
205
|
-
break
|
205
|
+
break unless node = node.parent
|
206
206
|
end
|
207
207
|
|
208
|
-
|
209
|
-
node.swap_position_with! leaf
|
208
|
+
original_node.swap_position_with! LeafNode.new
|
210
209
|
end
|
211
210
|
end
|
212
211
|
|
213
|
-
|
212
|
+
original_node.validate_free!
|
214
213
|
|
215
214
|
decrement_size!
|
216
215
|
update_left_most_node!
|
@@ -218,12 +217,90 @@ class RedBlackTree
|
|
218
217
|
self
|
219
218
|
end
|
220
219
|
|
221
|
-
|
220
|
+
# Searches for a node which matches the given data/value.
|
221
|
+
#
|
222
|
+
# @param data [any] the data to search for
|
223
|
+
# @yield [RedBlackTree::Node] the block to be used for comparison
|
224
|
+
# @return [RedBlackTree::Node, nil] the matching node
|
225
|
+
def search data = nil, &block
|
226
|
+
if block_given?
|
227
|
+
raise ArgumentError, "provide either data or block, not both" if data
|
228
|
+
|
229
|
+
_search_by_block block, @root
|
230
|
+
else
|
231
|
+
raise ArgumentError, "data must be provided for search" unless data
|
222
232
|
|
223
|
-
|
224
|
-
|
233
|
+
_search_by_data data, @root
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns true if there is a node which matches the given data/value, and false if there is not.
|
238
|
+
#
|
239
|
+
# @return [true, false]
|
240
|
+
def include? data
|
241
|
+
!!search(data)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Traverses the tree in pre-order and yields each node.
|
245
|
+
#
|
246
|
+
# @param node [RedBlackTree::Node] the node to start the traversal from
|
247
|
+
# @yield [RedBlackTree::Node] the block to be executed for each node
|
248
|
+
# @return [void]
|
249
|
+
def traverse_pre_order node = @root, &block
|
250
|
+
return if node.nil? || node.leaf?
|
251
|
+
|
252
|
+
block.call node
|
253
|
+
traverse_pre_order node.left, &block
|
254
|
+
traverse_pre_order node.right, &block
|
255
|
+
end
|
256
|
+
|
257
|
+
# Traverses the tree in in-order and yields each node.
|
258
|
+
#
|
259
|
+
# @param node [RedBlackTree::Node] the node to start the traversal from
|
260
|
+
# @yield [RedBlackTree::Node] the block to be executed for each node
|
261
|
+
# @return [void]
|
262
|
+
def traverse_in_order node = @root, &block
|
263
|
+
return if node.nil? || node.leaf?
|
264
|
+
|
265
|
+
traverse_in_order node.left, &block
|
266
|
+
block.call node
|
267
|
+
traverse_in_order node.right, &block
|
268
|
+
end
|
269
|
+
alias_method :traverse, :traverse_in_order
|
270
|
+
|
271
|
+
# Traverses the tree in post-order and yields each node.
|
272
|
+
#
|
273
|
+
# @param node [RedBlackTree::Node] the node to start the traversal from
|
274
|
+
# @yield [RedBlackTree::Node] the block to be executed for each node
|
275
|
+
# @return [void]
|
276
|
+
def traverse_post_order node = @root, &block
|
277
|
+
return if node.nil? || node.leaf?
|
278
|
+
|
279
|
+
traverse_post_order node.left, &block
|
280
|
+
traverse_post_order node.right, &block
|
281
|
+
block.call node
|
282
|
+
end
|
283
|
+
|
284
|
+
# Traverses the tree in level-order and yields each node.
|
285
|
+
#
|
286
|
+
# @yield [RedBlackTree::Node] the block to be executed for each node
|
287
|
+
# @return [void]
|
288
|
+
def traverse_level_order &block
|
289
|
+
return if @root.nil?
|
290
|
+
|
291
|
+
queue = [@root]
|
292
|
+
until queue.empty?
|
293
|
+
node = queue.shift
|
294
|
+
next if node.nil? || node.leaf?
|
295
|
+
|
296
|
+
block.call node
|
297
|
+
queue << node.left
|
298
|
+
queue << node.right
|
299
|
+
end
|
225
300
|
end
|
226
301
|
|
302
|
+
private
|
303
|
+
|
227
304
|
# Rotates a (sub-)tree starting from the given node in the given direction.
|
228
305
|
#
|
229
306
|
# @param node [RedBlackTree::Node] the root node of the sub-tree
|
@@ -250,6 +327,30 @@ class RedBlackTree
|
|
250
327
|
opp_direction_child
|
251
328
|
end
|
252
329
|
|
330
|
+
def _search_by_block block, node
|
331
|
+
traverse node do |current|
|
332
|
+
next if current.leaf?
|
333
|
+
|
334
|
+
return current if block.call current
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def _search_by_data data, node
|
339
|
+
return if node.nil? || node.leaf?
|
340
|
+
return node if data == node.data
|
341
|
+
|
342
|
+
mock_node = node.class.new data
|
343
|
+
if mock_node >= node
|
344
|
+
_search_by_data data, node.right
|
345
|
+
else
|
346
|
+
_search_by_data data, node.left
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def is_root? node
|
351
|
+
node && @root && node.object_id == @root.object_id
|
352
|
+
end
|
353
|
+
|
253
354
|
def increment_size!
|
254
355
|
@size += 1
|
255
356
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RedBlackTree
|
4
|
+
module DataDelegation
|
5
|
+
def method_missing method_name, *args, &block
|
6
|
+
if @data.respond_to? method_name
|
7
|
+
@data.public_send method_name, *args, &block
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing? method_name, include_private = false
|
14
|
+
@data.respond_to? method_name, include_private || super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -7,7 +7,7 @@ class RedBlackTree
|
|
7
7
|
case direction
|
8
8
|
when Node::LEFT then @left
|
9
9
|
when Node::RIGHT then @right
|
10
|
-
else raise ArgumentError, "Direction must be one of #{DIRECTIONS}"
|
10
|
+
else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}"
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -15,7 +15,7 @@ class RedBlackTree
|
|
15
15
|
case direction
|
16
16
|
when Node::LEFT then @left = node
|
17
17
|
when Node::RIGHT then @right = node
|
18
|
-
else raise ArgumentError, "Direction must be one of #{DIRECTIONS}"
|
18
|
+
else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
data/lib/red_black_tree/node.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "utils"
|
4
|
+
require_relative "node/leaf_node_comparable"
|
5
|
+
require_relative "node/data_delegation"
|
4
6
|
require_relative "node/left_right_element_referencers"
|
5
7
|
|
6
8
|
class RedBlackTree
|
7
9
|
class Node
|
10
|
+
class << self
|
11
|
+
def inherited subclass
|
12
|
+
subclass.prepend LeafNodeComparable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
8
16
|
include Comparable
|
17
|
+
include DataDelegation
|
9
18
|
|
10
19
|
# @return [any] the data/value representing the node
|
11
20
|
attr_reader :data
|
@@ -42,8 +51,8 @@ class RedBlackTree
|
|
42
51
|
RIGHT = "right"
|
43
52
|
DIRECTIONS = [LEFT, RIGHT].freeze
|
44
53
|
|
45
|
-
attr_reader :colour
|
46
54
|
attr_writer :data
|
55
|
+
attr_accessor :colour
|
47
56
|
attr_accessor :tree, :parent
|
48
57
|
attr_accessor :left, :right
|
49
58
|
include LeftRightElementReferencers
|
@@ -65,9 +74,9 @@ class RedBlackTree
|
|
65
74
|
def position
|
66
75
|
return unless @parent
|
67
76
|
|
68
|
-
case self
|
69
|
-
when @parent.left then LEFT
|
70
|
-
when @parent.right then RIGHT
|
77
|
+
case self.object_id
|
78
|
+
when @parent.left.object_id then LEFT
|
79
|
+
when @parent.right.object_id then RIGHT
|
71
80
|
else raise StructuralError, "Disowned by parent"
|
72
81
|
end
|
73
82
|
end
|
@@ -140,13 +149,13 @@ class RedBlackTree
|
|
140
149
|
self_position = position
|
141
150
|
other_position = other_node.position
|
142
151
|
|
143
|
-
if other_node.parent == self
|
152
|
+
if other_node.parent.object_id == self.object_id
|
144
153
|
self[other_position] = other_node[other_position]
|
145
154
|
other_node[other_position] = self
|
146
155
|
|
147
156
|
other_node.parent = @parent
|
148
157
|
@parent = other_node
|
149
|
-
elsif other_node == @parent
|
158
|
+
elsif other_node.object_id == @parent.object_id
|
150
159
|
other_node[self_position] = self[self_position]
|
151
160
|
self[self_position] = other_node
|
152
161
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Young
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -23,7 +23,9 @@ files:
|
|
23
23
|
- README.md
|
24
24
|
- lib/red-black-tree.rb
|
25
25
|
- lib/red_black_tree/node.rb
|
26
|
+
- lib/red_black_tree/node/data_delegation.rb
|
26
27
|
- lib/red_black_tree/node/leaf_node.rb
|
28
|
+
- lib/red_black_tree/node/leaf_node_comparable.rb
|
27
29
|
- lib/red_black_tree/node/left_right_element_referencers.rb
|
28
30
|
- lib/red_black_tree/utils.rb
|
29
31
|
- lib/red_black_tree/version.rb
|
@@ -50,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
52
|
- !ruby/object:Gem::Version
|
51
53
|
version: '0'
|
52
54
|
requirements: []
|
53
|
-
rubygems_version: 3.5.
|
55
|
+
rubygems_version: 3.5.16
|
54
56
|
signing_key:
|
55
57
|
specification_version: 4
|
56
58
|
summary: Red-Black Tree Data Structure for Ruby
|