red-black-tree 0.1.7 → 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: 291198bed7dd82b504c47b1c6fa047674c9302a7c529d33409d61797a0677929
4
- data.tar.gz: ab8b8d513625de270d0126c0c3f1d5d787260b32f1bd0f13927332e9e2640aab
3
+ metadata.gz: e7cc7e3138c1b0ceea2d6b5a2f91620323f75ebe346288bc69761d77d95eb5c4
4
+ data.tar.gz: 057d9d283d5312f6c5f8e155e3c039c342d036b779d115738c7afbc57ca419f4
5
5
  SHA512:
6
- metadata.gz: aed7c4bd38468df481ebf815605f8564bc66ecfb8b7a53ee5babefaad2f68fc0a08994a9e91922f161f628a23d63eff02e57c7418bd7ce80b6db4f95723aa421
7
- data.tar.gz: 2ec1c08f09e3ef1ca7bf608032cfb4a28042e57ec357dd40ee1c8951c64e0c7d944f7900db4bf9acc758513070f5533745e4b6a3fa4477567644de73cc62787c
6
+ metadata.gz: 61f4a9a44d3949b3c2072de4fbca1507cf78306adc32dae8bad26ec5a71171ae952cb81effddf41990a8e2b6dc0e06d39a121389e5bc4c3438eca981dc2a0b59
7
+ data.tar.gz: c9bced3565a3ec9439c1c08dca09676d1c3966cb18a97507c1f815e938b73419f9f3bce0e4d13d06e97dc59a92f845a3b9b4f094841aa900bde3b004959baeca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.8] - 2025-07-26
4
+
5
+ - Refactor to slim down and de-duplicate some methods
6
+
3
7
  ## [0.1.7] - 2025-01-05
4
8
 
5
9
  - Change `RedBlackTree#delete!` return value to the deleted node
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
@@ -106,27 +106,7 @@ class RedBlackTree
106
106
  node.parent = target_parent
107
107
  node.red!
108
108
 
109
- while node.parent && node.parent.red? do
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
109
+ rebalance_after_insertion! node
130
110
  end
131
111
 
132
112
  node.validate! @root == node
@@ -153,71 +133,11 @@ class RedBlackTree
153
133
  original_node = node
154
134
 
155
135
  if node.children_are_valid?
156
- is_root = is_root? node
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
136
+ delete_node_with_two_children! node
165
137
  elsif node.single_child_is_valid?
166
- is_root = is_root? node
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
138
+ delete_node_with_one_child! node
174
139
  elsif node.children_are_leaves?
175
- if is_root? node
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
140
+ delete_leaf_node! node, original_node
221
141
  end
222
142
 
223
143
  original_node.tree = nil
@@ -270,7 +190,7 @@ class RedBlackTree
270
190
  #
271
191
  # @return [true, false]
272
192
  def include? data
273
- !!search(data)
193
+ !search(data).nil?
274
194
  end
275
195
 
276
196
  # Traverses the tree in pre-order and yields each node.
@@ -333,6 +253,29 @@ class RedBlackTree
333
253
 
334
254
  private
335
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
+
336
279
  # Rotates a (sub-)tree starting from the given node in the given direction.
337
280
  #
338
281
  # @param node [RedBlackTree::Node] the root node of the sub-tree
@@ -359,12 +302,111 @@ class RedBlackTree
359
302
  opp_direction_child
360
303
  end
361
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
+
362
404
  def _search_by_data data, node
363
405
  return if node.nil? || node.leaf?
364
406
  return node if data == node.data
365
407
 
366
- mock_node = node.class.new data
367
- if mock_node >= node
408
+ comparison = data <=> node.data
409
+ if comparison && comparison >= 0
368
410
  _search_by_data data, node.right
369
411
  else
370
412
  _search_by_data data, node.left
@@ -388,27 +430,4 @@ class RedBlackTree
388
430
  end
389
431
  end
390
432
  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
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedBlackTree
4
- VERSION = "0.1.7"
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.7
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-05 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: []