red-black-tree 0.1.6 → 0.1.8

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: da41036d61ffc0739627740b631dbb00df76ee87a5db6145ad508569552703c3
4
- data.tar.gz: 99172cd947f7714788dadd94068ff637bdfa510ae78367a4689a892dc4ad1a05
3
+ metadata.gz: e7cc7e3138c1b0ceea2d6b5a2f91620323f75ebe346288bc69761d77d95eb5c4
4
+ data.tar.gz: 057d9d283d5312f6c5f8e155e3c039c342d036b779d115738c7afbc57ca419f4
5
5
  SHA512:
6
- metadata.gz: 96e4b84d481b71cf8f3e45b728d19c1417f50c68ea9a9b8acdbd35a3514c8ab154df42ef7b3542ff0655e45c76a2f39b2a4a7ad6131a2c238fd7b126bddc29bd
7
- data.tar.gz: 54a7743f84d5748e830dee7ed27c66f34c9517d4977b496c74cc6aa194b0fbef175f5ad7bd3d7420ce64f68298ff4bf529b9d1664966c94007db5fd1cc76d35d
6
+ metadata.gz: 61f4a9a44d3949b3c2072de4fbca1507cf78306adc32dae8bad26ec5a71171ae952cb81effddf41990a8e2b6dc0e06d39a121389e5bc4c3438eca981dc2a0b59
7
+ data.tar.gz: c9bced3565a3ec9439c1c08dca09676d1c3966cb18a97507c1f815e938b73419f9f3bce0e4d13d06e97dc59a92f845a3b9b4f094841aa900bde3b004959baeca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.8] - 2025-07-26
4
+
5
+ - Refactor to slim down and de-duplicate some methods
6
+
7
+ ## [0.1.7] - 2025-01-05
8
+
9
+ - Change `RedBlackTree#delete!` return value to the deleted node
10
+ - Add `RedBlackTree#clear!`
11
+
3
12
  ## [0.1.6] - 2025-01-01
4
13
 
5
14
  - Prefer node deletion over data swapping
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # RedBlackTree
2
2
 
3
+ ![Version](https://img.shields.io/gem/v/red-black-tree)
4
+ ![Build](https://img.shields.io/github/actions/workflow/status/joshuay03/red-black-tree/.github/workflows/tests.yml?branch=main)
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
@@ -187,7 +190,6 @@ end
187
190
 
188
191
  - `RedBlackTree#max`
189
192
  - `RedBlackTree#height`
190
- - `RedBlackTree#clear`
191
193
 
192
194
  ## Development
193
195
 
@@ -20,6 +20,7 @@ class RedBlackTree
20
20
 
21
21
  def initialize
22
22
  @size = 0
23
+ @root = nil
23
24
  @left_most_node = nil
24
25
  end
25
26
 
@@ -90,6 +91,7 @@ class RedBlackTree
90
91
  raise ArgumentError, "Target parent already has #{direction} child" if (child = target_parent[direction]) && child.valid?
91
92
  end
92
93
 
94
+ node.tree = self
93
95
  node.parent = nil
94
96
  node.left = LeafNode.new
95
97
  node.left.parent = node
@@ -104,27 +106,7 @@ class RedBlackTree
104
106
  node.parent = target_parent
105
107
  node.red!
106
108
 
107
- while node.parent && node.parent.red? do
108
- if node.parent.sibling && node.parent.sibling.red?
109
- node.parent.black!
110
- node.parent.sibling.black!
111
- node.parent.parent.red!
112
- node = node.parent.parent
113
- else
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]
118
- end
119
-
120
- opp_direction = node.opposite_position
121
- rotate_sub_tree! node.parent.parent, opp_direction
122
- node.parent.black!
123
- node.parent[opp_direction].red!
124
- end
125
-
126
- @root.black!
127
- end
109
+ rebalance_after_insertion! node
128
110
  end
129
111
 
130
112
  node.validate! @root == node
@@ -138,99 +120,43 @@ class RedBlackTree
138
120
  # Removes the given node from the tree.
139
121
  #
140
122
  # @param node [RedBlackTree::Node] the node to be removed
141
- # @return [RedBlackTree] self
123
+ # @return [RedBlackTree::Node, nil] the removed node
142
124
  def delete! node
143
125
  raise ArgumentError, "cannot delete leaf node" if node.instance_of? LeafNode
144
126
 
127
+ if node.tree.nil?
128
+ return
129
+ elsif node.tree != self
130
+ raise ArgumentError, "node does not belong to this tree"
131
+ end
132
+
145
133
  original_node = node
146
134
 
147
135
  if node.children_are_valid?
148
- is_root = is_root? node
149
-
150
- successor = node.left
151
- successor = successor.left until successor.left.leaf?
152
- node.swap_colour_with! successor
153
- node.swap_position_with! successor
154
- node.swap_position_with! LeafNode.new
155
-
156
- @root = successor if is_root
157
-
158
- original_node.validate_free!
159
-
160
- decrement_size!
161
- update_left_most_node!
162
-
163
- return self
136
+ delete_node_with_two_children! node
164
137
  elsif node.single_child_is_valid?
165
- is_root = is_root? node
166
-
167
- valid_child = node.children.find(&:valid?)
168
- valid_child.black!
169
- node.swap_position_with! valid_child
170
- node.swap_position_with! LeafNode.new
171
-
172
- @root = valid_child if is_root
173
-
174
- original_node.validate_free!
175
-
176
- decrement_size!
177
- update_left_most_node!
178
-
179
- return self
138
+ delete_node_with_one_child! node
180
139
  elsif node.children_are_leaves?
181
- if is_root? node
182
- @root = nil
183
- elsif node.red?
184
- node.swap_position_with! LeafNode.new
185
- else
186
- loop do
187
- if node.sibling && node.sibling.valid? && node.sibling.red?
188
- node.parent.red!
189
- node.sibling.black!
190
- rotate_sub_tree! node.parent, node.position
191
- end
192
-
193
- if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
194
- node.sibling.red! unless node.sibling.leaf?
195
- node.close_nephew.black!
196
- rotate_sub_tree! node.sibling, node.opposite_position
197
- end
198
-
199
- if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
200
- case node.parent.colour
201
- when Node::RED then node.sibling.red!
202
- when Node::BLACK then node.sibling.black!
203
- end
204
- node.parent.black!
205
- node.distant_nephew.black!
206
- rotate_sub_tree! node.parent, node.position
207
-
208
- break
209
- end
210
-
211
- if node.parent && node.parent.red?
212
- node.sibling.red! unless node.sibling.leaf?
213
- node.parent.black!
214
-
215
- break
216
- end
217
-
218
- if node.sibling && !node.sibling.leaf?
219
- node.sibling.red!
220
- end
221
-
222
- break unless node = node.parent
223
- end
224
-
225
- original_node.swap_position_with! LeafNode.new
226
- end
140
+ delete_leaf_node! node, original_node
227
141
  end
228
142
 
143
+ original_node.tree = nil
229
144
  original_node.validate_free!
230
145
 
231
146
  decrement_size!
232
147
  update_left_most_node!
233
148
 
149
+ original_node
150
+ end
151
+
152
+ # Removes all nodes from the tree.
153
+ #
154
+ # @return [RedBlackTree] self
155
+ def clear!
156
+ @root = nil
157
+ @size = 0
158
+ @left_most_node = nil
159
+
234
160
  self
235
161
  end
236
162
 
@@ -264,7 +190,7 @@ class RedBlackTree
264
190
  #
265
191
  # @return [true, false]
266
192
  def include? data
267
- !!search(data)
193
+ !search(data).nil?
268
194
  end
269
195
 
270
196
  # Traverses the tree in pre-order and yields each node.
@@ -327,6 +253,29 @@ class RedBlackTree
327
253
 
328
254
  private
329
255
 
256
+ def is_root? node
257
+ node && @root && node.object_id == @root.object_id
258
+ end
259
+
260
+ def increment_size!
261
+ @size += 1
262
+ end
263
+
264
+ def decrement_size!
265
+ @size -= 1
266
+ end
267
+
268
+ def update_left_most_node!
269
+ unless @root
270
+ @left_most_node = nil
271
+ return
272
+ end
273
+
274
+ current = @root
275
+ current = current.left until current.left.leaf?
276
+ @left_most_node = current
277
+ end
278
+
330
279
  # Rotates a (sub-)tree starting from the given node in the given direction.
331
280
  #
332
281
  # @param node [RedBlackTree::Node] the root node of the sub-tree
@@ -353,12 +302,111 @@ class RedBlackTree
353
302
  opp_direction_child
354
303
  end
355
304
 
305
+ def rebalance_after_insertion! node
306
+ while node.parent && node.parent.red? do
307
+ if node.parent.sibling && node.parent.sibling.red?
308
+ node.parent.black!
309
+ node.parent.sibling.black!
310
+ node.parent.parent.red!
311
+ node = node.parent.parent
312
+ else
313
+ opp_direction = node.opposite_position
314
+ if node.parent.position == opp_direction
315
+ rotate_sub_tree! node.parent, opp_direction
316
+ node = node[opp_direction]
317
+ end
318
+
319
+ opp_direction = node.opposite_position
320
+ rotate_sub_tree! node.parent.parent, opp_direction
321
+ node.parent.black!
322
+ node.parent[opp_direction].red!
323
+ end
324
+
325
+ @root.black!
326
+ end
327
+ end
328
+
329
+ def delete_node_with_two_children! node
330
+ is_root = is_root? node
331
+
332
+ successor = node.left
333
+ successor = successor.right until successor.right.leaf?
334
+ node.swap_colour_with! successor
335
+ node.swap_position_with! successor
336
+ node.swap_position_with! LeafNode.new
337
+
338
+ @root = successor if is_root
339
+ end
340
+
341
+ def delete_node_with_one_child! node
342
+ is_root = is_root? node
343
+
344
+ valid_child = node.children.find(&:valid?)
345
+ valid_child.black!
346
+ node.swap_position_with! valid_child
347
+ node.swap_position_with! LeafNode.new
348
+
349
+ @root = valid_child if is_root
350
+ end
351
+
352
+ def delete_leaf_node! node, original_node
353
+ if is_root? node
354
+ @root = nil
355
+ elsif node.red?
356
+ node.swap_position_with! LeafNode.new
357
+ else
358
+ rebalance_after_deletion! node
359
+ original_node.swap_position_with! LeafNode.new
360
+ end
361
+ end
362
+
363
+ def rebalance_after_deletion! node
364
+ loop do
365
+ if node.sibling && node.sibling.valid? && node.sibling.red?
366
+ node.parent.red!
367
+ node.sibling.black!
368
+ rotate_sub_tree! node.parent, node.position
369
+ end
370
+
371
+ if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
372
+ node.sibling.red! unless node.sibling.leaf?
373
+ node.close_nephew.black!
374
+ rotate_sub_tree! node.sibling, node.opposite_position
375
+ end
376
+
377
+ if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
378
+ case node.parent.colour
379
+ when Node::RED then node.sibling.red!
380
+ when Node::BLACK then node.sibling.black!
381
+ end
382
+ node.parent.black!
383
+ node.distant_nephew.black!
384
+ rotate_sub_tree! node.parent, node.position
385
+
386
+ break
387
+ end
388
+
389
+ if node.parent && node.parent.red?
390
+ node.sibling.red! unless node.sibling.leaf?
391
+ node.parent.black!
392
+
393
+ break
394
+ end
395
+
396
+ if node.sibling && !node.sibling.leaf?
397
+ node.sibling.red!
398
+ end
399
+
400
+ break unless node = node.parent
401
+ end
402
+ end
403
+
356
404
  def _search_by_data data, node
357
405
  return if node.nil? || node.leaf?
358
406
  return node if data == node.data
359
407
 
360
- mock_node = node.class.new data
361
- if mock_node >= node
408
+ comparison = data <=> node.data
409
+ if comparison && comparison >= 0
362
410
  _search_by_data data, node.right
363
411
  else
364
412
  _search_by_data data, node.left
@@ -382,27 +430,4 @@ class RedBlackTree
382
430
  end
383
431
  end
384
432
  end
385
-
386
- def is_root? node
387
- node && @root && node.object_id == @root.object_id
388
- end
389
-
390
- def increment_size!
391
- @size += 1
392
- end
393
-
394
- def decrement_size!
395
- @size -= 1
396
- end
397
-
398
- def update_left_most_node!
399
- unless @root
400
- @left_most_node = nil
401
- return
402
- end
403
-
404
- current = @root
405
- current = current.left until current.left.leaf?
406
- @left_most_node = current
407
- end
408
433
  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
@@ -19,6 +19,9 @@ class RedBlackTree
19
19
  # @return [any] the data/value representing the node
20
20
  attr_reader :data
21
21
 
22
+ # @return [RedBlackTree::Node, nil] the tree this node belongs to
23
+ attr_reader :tree
24
+
22
25
  # @param data [any] a non-nil data/value representing the node
23
26
  # @return [Node] a new instance of Node
24
27
  def initialize data
@@ -51,10 +54,8 @@ class RedBlackTree
51
54
  RIGHT = "right"
52
55
  DIRECTIONS = [LEFT, RIGHT].freeze
53
56
 
54
- attr_writer :data
55
- attr_accessor :colour
56
- attr_accessor :tree, :parent
57
- attr_accessor :left, :right
57
+ attr_writer :data, :tree
58
+ attr_accessor :colour, :parent, :left, :right
58
59
  include LeftRightElementReferencers
59
60
 
60
61
  def red? = @colour == RED
@@ -116,6 +117,8 @@ class RedBlackTree
116
117
  end
117
118
 
118
119
  def validate_free!
120
+ raise StructuralError, "Node is still chained to a tree" if @tree
121
+
119
122
  anchors = []
120
123
  anchors << "parent" if @parent
121
124
  anchors << "left child" if @left && @left.valid?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedBlackTree
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  end
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.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-01 00:00:00.000000000 Z
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: 3.6.2
52
+ rubygems_version: 3.6.9
53
53
  specification_version: 4
54
54
  summary: Red-Black Tree Data Structure for Ruby
55
55
  test_files: []