red-black-tree 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +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
|