red-black-tree 0.1.1 → 0.1.2

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: 61a6b03da9c3a60295a236ee3a62996b780cb19dfc18f85fd050e5eb788cf2a0
4
- data.tar.gz: 05a1325effc0127636d0b9ad4e837e1b319c248488b20b4023541bbdea940f29
3
+ metadata.gz: e609f03254a0ff34b07adf5445e91803cf4f50ffc5ba91b6288c31003218b87a
4
+ data.tar.gz: e1289ab4cefa28f5ae2eca05be45d5a7dbd2fa13a8a5256c81ac21511bb8e0fb
5
5
  SHA512:
6
- metadata.gz: 19aaf38a058f020b0638ddb99cfa3ecaa51d9bc6a0a0f82046f577898e9de6cf203a3d8e48c2d3dbba60bbf3b555ecd8c47e5cffbe9f8cf0840740af34698924
7
- data.tar.gz: 759e5478416fc7c5a80e4c0775d4df53d9640ddc356b3eea65595ae3657d0a8314c0ff7273799c8f6f0ed31ca6b5bf3e2431b31008f17537445aca2b6b694807
6
+ metadata.gz: 514eee78de1f75f5caaa55d285109d5e64a431a95732b3e6cabbee23e523ddd93b8b1e5e996c4ebcebce86e0ac54a15e0b88299a295515ce592254706f905e1a
7
+ data.tar.gz: '092aaa352db1d9d8d1082393bfff927c35dbb349ffc980720835379c27297f7d47f817edb2eb29a22103bc4beda53c683150b05756444d75b823e673161358e1'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2024-09-08
4
+
5
+ - Fix a bunch of issues in `RedBlackTree#insert!` and `RedBlackTree#delete!` algorithms
6
+ - Fix `RedBlackTree::LeafNode`s being marked red
7
+ - Handle comparison with `RedBlackTree::LeafNode` in subclasses of `RedBlackTree::Node`
8
+ - Add `RedBlackTree#include?`
9
+ - Add `RedBlackTree#search`
10
+ - Alias `RedBlackTree#left_most_node` as `RedBlackTree#min`
11
+
3
12
  ## [0.1.1] - 2024-08-04
4
13
 
5
14
  - 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
- if other.instance_of? RedBlackTree::LeafNode
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,17 @@ until tree.empty?
40
37
  end
41
38
  ```
42
39
 
40
+ ## WIP Features
41
+
42
+ - `RedBlackTree#traverse_in_order`
43
+ - `RedBlackTree#traverse_pre_order`
44
+ - `RedBlackTree#traverse_post_order`
45
+ - `RedBlackTree#traverse_level_order`
46
+ - `RedBlackTree#traverse` (default in-order)
47
+ - `RedBlackTree#max`
48
+ - `RedBlackTree#height`
49
+ - `RedBlackTree#clear`
50
+
43
51
  ## Development
44
52
 
45
53
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the
@@ -17,6 +17,7 @@ class RedBlackTree
17
17
  # @private
18
18
  # @return [RedBlackTree::Node, nil] the left most node
19
19
  attr_reader :left_most_node
20
+ alias_method :min, :left_most_node
20
21
 
21
22
  def initialize
22
23
  @size = 0
@@ -41,8 +42,13 @@ class RedBlackTree
41
42
  #
42
43
  # @return [RedBlackTree::Node, nil] the removed node
43
44
  def shift
44
- node = @left_most_node
45
- delete! node if node
45
+ return unless @left_most_node
46
+
47
+ node = @left_most_node.dup
48
+ node.colour = node.parent = node.left = node.right = nil
49
+
50
+ delete! @left_most_node
51
+
46
52
  node
47
53
  end
48
54
 
@@ -85,8 +91,6 @@ class RedBlackTree
85
91
  raise ArgumentError, "Target parent already has #{direction} child" if (child = target_parent[direction]) && child.valid?
86
92
  end
87
93
 
88
- opp_direction = opposite_direction direction if direction
89
-
90
94
  node.parent = nil
91
95
  node.left = LeafNode.new
92
96
  node.left.parent = node
@@ -108,17 +112,16 @@ class RedBlackTree
108
112
  node.parent.parent.red!
109
113
  node = node.parent.parent
110
114
  else
111
- if node.position == direction
112
- node = node.parent
113
- rotate_sub_tree! node, opp_direction
115
+ opp_direction = node.opposite_position
116
+ if node.parent.position == opp_direction
117
+ rotate_sub_tree! node.parent, opp_direction
118
+ node = node[opp_direction]
114
119
  end
115
120
 
121
+ opp_direction = node.opposite_position
122
+ rotate_sub_tree! node.parent.parent, opp_direction
116
123
  node.parent.black!
117
-
118
- if node.parent.parent
119
- node.parent.parent.red!
120
- rotate_sub_tree! node.parent.parent, direction
121
- end
124
+ node.parent[opp_direction].red!
122
125
  end
123
126
 
124
127
  @root.black!
@@ -140,6 +143,8 @@ class RedBlackTree
140
143
  def delete! node
141
144
  raise ArgumentError, "cannot delete leaf node" if node.instance_of? LeafNode
142
145
 
146
+ original_node = node
147
+
143
148
  if node.children_are_valid?
144
149
  successor = node.left
145
150
  successor = successor.left until successor.left.leaf?
@@ -160,19 +165,19 @@ class RedBlackTree
160
165
  if is_root? node
161
166
  @root = nil
162
167
  elsif node.red?
163
- leaf = LeafNode.new
164
- node.swap_position_with! leaf
168
+ node.swap_position_with! LeafNode.new
165
169
  else
166
- direction = node.position
167
- opp_direction = opposite_direction direction
168
-
169
170
  loop do
170
- if node.sibling.valid? && node.sibling.red?
171
+ if node.sibling && node.sibling.valid? && node.sibling.red?
171
172
  node.parent.red!
172
173
  node.sibling.black!
173
- rotate_sub_tree! node.parent, direction
174
+ rotate_sub_tree! node.parent, node.position
175
+ end
174
176
 
175
- next
177
+ if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
178
+ node.sibling.red! unless node.sibling.leaf?
179
+ node.close_nephew.black!
180
+ rotate_sub_tree! node.sibling, node.opposite_position
176
181
  end
177
182
 
178
183
  if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
@@ -182,35 +187,30 @@ class RedBlackTree
182
187
  end
183
188
  node.parent.black!
184
189
  node.distant_nephew.black!
185
- rotate_sub_tree! node.parent, direction
190
+ rotate_sub_tree! node.parent, node.position
186
191
 
187
192
  break
188
193
  end
189
194
 
190
- if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
191
- node.sibling.red!
192
- node.close_nephew.black!
193
- rotate_sub_tree! node.sibling, opp_direction
195
+ if node.parent && node.parent.red?
196
+ node.sibling.red! unless node.sibling.leaf?
197
+ node.parent.black!
194
198
 
195
- next
199
+ break
196
200
  end
197
201
 
198
- if node.parent.red?
202
+ if node.sibling && !node.sibling.leaf?
199
203
  node.sibling.red!
200
- node.parent.black!
201
-
202
- break
203
204
  end
204
205
 
205
- break
206
+ break unless node = node.parent
206
207
  end
207
208
 
208
- leaf = LeafNode.new
209
- node.swap_position_with! leaf
209
+ original_node.swap_position_with! LeafNode.new
210
210
  end
211
211
  end
212
212
 
213
- node.validate_free!
213
+ original_node.validate_free!
214
214
 
215
215
  decrement_size!
216
216
  update_left_most_node!
@@ -218,12 +218,25 @@ class RedBlackTree
218
218
  self
219
219
  end
220
220
 
221
- private
221
+ # Searches for a node which matches the given data/value.
222
+ #
223
+ # @param data [any] the data to search for
224
+ # @return [RedBlackTree::Node, nil] the matching node
225
+ def search data
226
+ raise ArgumentError, "data must be provided for search" unless data
222
227
 
223
- def is_root? node
224
- node.object_id == @root.object_id
228
+ _search data, @root
225
229
  end
226
230
 
231
+ # Returns true if there is a node which matches the given data/value, and false if there is not.
232
+ #
233
+ # @return [true, false]
234
+ def include? data
235
+ !!search(data)
236
+ end
237
+
238
+ private
239
+
227
240
  # Rotates a (sub-)tree starting from the given node in the given direction.
228
241
  #
229
242
  # @param node [RedBlackTree::Node] the root node of the sub-tree
@@ -250,6 +263,22 @@ class RedBlackTree
250
263
  opp_direction_child
251
264
  end
252
265
 
266
+ def _search data, current_node
267
+ return if current_node.nil? || current_node.leaf?
268
+ return current_node if data == current_node.data
269
+
270
+ mock_node = current_node.class.new data
271
+ if mock_node >= current_node
272
+ _search data, current_node.right
273
+ else
274
+ _search data, current_node.left
275
+ end
276
+ end
277
+
278
+ def is_root? node
279
+ node && @root && node.object_id == @root.object_id
280
+ end
281
+
253
282
  def increment_size!
254
283
  @size += 1
255
284
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedBlackTree
4
+ module LeafNodeComparable # @private
5
+ def <=> other
6
+ if other.instance_of? ::RedBlackTree::LeafNode
7
+ 1
8
+ else
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,10 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "utils"
4
+ require_relative "node/leaf_node_comparable"
4
5
  require_relative "node/left_right_element_referencers"
5
6
 
6
7
  class RedBlackTree
7
8
  class Node
9
+ class << self
10
+ def inherited subclass
11
+ subclass.prepend LeafNodeComparable
12
+ end
13
+ end
14
+
8
15
  include Comparable
9
16
 
10
17
  # @return [any] the data/value representing the node
@@ -42,8 +49,8 @@ class RedBlackTree
42
49
  RIGHT = "right"
43
50
  DIRECTIONS = [LEFT, RIGHT].freeze
44
51
 
45
- attr_reader :colour
46
52
  attr_writer :data
53
+ attr_accessor :colour
47
54
  attr_accessor :tree, :parent
48
55
  attr_accessor :left, :right
49
56
  include LeftRightElementReferencers
@@ -65,9 +72,9 @@ class RedBlackTree
65
72
  def position
66
73
  return unless @parent
67
74
 
68
- case self
69
- when @parent.left then LEFT
70
- when @parent.right then RIGHT
75
+ case self.object_id
76
+ when @parent.left.object_id then LEFT
77
+ when @parent.right.object_id then RIGHT
71
78
  else raise StructuralError, "Disowned by parent"
72
79
  end
73
80
  end
@@ -140,13 +147,13 @@ class RedBlackTree
140
147
  self_position = position
141
148
  other_position = other_node.position
142
149
 
143
- if other_node.parent == self
150
+ if other_node.parent.object_id == self.object_id
144
151
  self[other_position] = other_node[other_position]
145
152
  other_node[other_position] = self
146
153
 
147
154
  other_node.parent = @parent
148
155
  @parent = other_node
149
- elsif other_node == @parent
156
+ elsif other_node.object_id == @parent.object_id
150
157
  other_node[self_position] = self[self_position]
151
158
  self[self_position] = other_node
152
159
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedBlackTree
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
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.1
4
+ version: 0.1.2
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-08-03 00:00:00.000000000 Z
11
+ date: 2024-09-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -24,6 +24,7 @@ files:
24
24
  - lib/red-black-tree.rb
25
25
  - lib/red_black_tree/node.rb
26
26
  - lib/red_black_tree/node/leaf_node.rb
27
+ - lib/red_black_tree/node/leaf_node_comparable.rb
27
28
  - lib/red_black_tree/node/left_right_element_referencers.rb
28
29
  - lib/red_black_tree/utils.rb
29
30
  - lib/red_black_tree/version.rb