Skip to content

Commit ddb7989

Browse files
committed
Harden binary child assignment
Fix set_child_at index errors and clean up parent/hash references when replacing or clearing children. This preserves swap semantics and avoids stale lookups.
1 parent 95f1372 commit ddb7989

2 files changed

Lines changed: 34 additions & 5 deletions

File tree

lib/tree/binarytree.rb

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,29 @@ def inordered_each
203203
#
204204
# @raise [ArgumentError] If the index is out of limits.
205205
def set_child_at(child, at_index)
206-
raise ArgumentError 'A binary tree cannot have more than two children.'\
207-
unless (0..1).include? at_index
206+
raise ArgumentError, 'A binary tree cannot have more than two children.'\
207+
unless (0..1).include? at_index
208208

209-
@children[at_index] = child
210-
@children_hash[child.name] = child if child # Assign the name mapping
211-
child.parent = self if child
209+
old_child = @children[at_index]
210+
if old_child && old_child != child
211+
still_present = @children.each_with_index.any? do |existing, idx|
212+
idx != at_index && existing.equal?(old_child)
213+
end
214+
215+
unless still_present
216+
@children_hash.delete(old_child.name)
217+
old_child.set_as_root!
218+
end
219+
end
220+
221+
if child
222+
child.parent&.remove!(child) unless child.parent == self
223+
@children[at_index] = child
224+
@children_hash[child.name] = child # Assign the name mapping
225+
child.parent = self
226+
else
227+
@children[at_index] = nil
228+
end
212229
child
213230
end
214231

test/test_binarytree.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ def test_left_child_equals
229229
assert_nil(@root.left_child, 'The left child should now be nil')
230230
assert_nil(@root.first_child, 'The first child is now nil')
231231
assert_equal('B Child at Right', @root.last_child.name, 'The last child should now be the right child')
232+
assert(@left_child1.root?, 'The old left child should now be a root')
233+
assert_nil(@left_child1.parent, 'The old left child should not have a parent')
234+
assert_nil(@root['A Child at Left'], 'Lookup by old left name should be nil')
232235
end
233236

234237
# Test right_child= method.
@@ -249,6 +252,15 @@ def test_right_child_equals
249252
assert_nil(@root.right_child, 'The right child should now be nil')
250253
assert_equal('A Child at Left', @root.first_child.name, 'The first child should now be the left child')
251254
assert_nil(@root.last_child, 'The first child is now nil')
255+
assert(@right_child1.root?, 'The old right child should now be a root')
256+
assert_nil(@right_child1.parent, 'The old right child should not have a parent')
257+
assert_nil(@root['B Child at Right'], 'Lookup by old right name should be nil')
258+
end
259+
260+
# Test invalid index error for set_child_at.
261+
def test_set_child_at_invalid_index
262+
error = assert_raise(ArgumentError) { @root.send(:set_child_at, @left_child1, 2) }
263+
assert_match(/cannot have more than two children/i, error.message)
252264
end
253265

254266
# Test isLeft_child? method.

0 commit comments

Comments
 (0)