Skip to content

Commit e53a05f

Browse files
StephenVavasisoxinabox
authored andcommitted
Fixed long-standing bug in balanced_tree.jl that corrupted SortedMult… (#775)
* Fixed long-standing bug in balanced_tree.jl that corrupted SortedMultiDict for certain insertions. * Added test case from issue #773
1 parent f2628b7 commit e53a05f

File tree

2 files changed

+123
-106
lines changed

2 files changed

+123
-106
lines changed

src/balanced_tree.jl

Lines changed: 97 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ end
354354
## done whether the iterm
355355
## is already in the tree, so insertion of a new item always succeeds.
356356

357+
357358
function Base.insert!(t::BalancedTree23{K,D,Ord}, k, d, allowdups::Bool) where {K,D,Ord <: Ordering}
358359

359360
## First we find the greatest data node that is <= k.
@@ -395,134 +396,122 @@ function Base.insert!(t::BalancedTree23{K,D,Ord}, k, d, allowdups::Bool) where {
395396
## go back and fix the parent.
396397

397398
newind = push_or_reuse!(t.data, t.freedatainds, KDRec{K,D}(0,k,d))
399+
push!(t.useddatacells, newind)
398400
p1 = parent
399-
oldchild = leafind
400401
newchild = newind
401402
minkeynewchild = k
402403
splitroot = false
403404
curdepth = depth
405+
existingchild = leafind
404406

407+
405408
## This loop ascends the tree (i.e., follows the path from a leaf to the root)
406409
## starting from the parent p1 of
407-
## where the new key k would go. For each 3-node we encounter
410+
## where the new key k will go.
411+
## Variables updated by the loop:
412+
## p1: parent of where the new node goes
413+
## newchild: index of the child to be inserted
414+
## minkeynewchild: the minimum key in the subtree rooted at newchild
415+
## existingchild: a child of p1; the newchild must
416+
## be inserted in the slot to the right of existingchild
417+
## curdepth: depth of newchild
418+
## For each 3-node we encounter
408419
## during the ascent, we add a new child, which requires splitting
409420
## the 3-node into two 2-nodes. Then we keep going until we hit the root.
410421
## If we encounter a 2-node, then the ascent can stop; we can
411-
## change the 2-node to a 3-node with the new child. Invariants
412-
## during this loop are:
413-
## p1: the parent node (a tree node index) where the insertion must occur
414-
## oldchild,newchild: the two children of the parent node; oldchild
415-
## was already in the tree; newchild was just added to it.
416-
## minkeynewchild: This is the key that is the minimum value in
417-
## the subtree rooted at newchild.
418-
419-
while t.tree[p1].child3 > 0
420-
isleaf = (curdepth == depth)
421-
oldtreenode = t.tree[p1]
422-
423-
## Node p1 index a 3-node. There are three cases for how to
424-
## insert new child. All three cases involve splitting the
425-
## existing node (oldtreenode, numbered p1) into
426-
## two new nodes. One keeps the index p1; the other has
427-
## has a new index called newparentnum.
428-
429-
430-
cmp = isleaf ? cmp3_leaf(ord, oldtreenode, minkeynewchild) :
431-
cmp3_nonleaf(ord, oldtreenode, minkeynewchild)
432-
433-
if cmp == 1
434-
lefttreenodenew = TreeNode{K}(oldtreenode.child1, newchild, 0,
435-
oldtreenode.parent,
436-
minkeynewchild, minkeynewchild)
437-
righttreenodenew = TreeNode{K}(oldtreenode.child2, oldtreenode.child3, 0,
438-
oldtreenode.parent, oldtreenode.splitkey2,
439-
oldtreenode.splitkey2)
440-
minkeynewchild = oldtreenode.splitkey1
441-
whichp = 1
442-
elseif cmp == 2
443-
lefttreenodenew = TreeNode{K}(oldtreenode.child1, oldtreenode.child2, 0,
444-
oldtreenode.parent,
445-
oldtreenode.splitkey1, oldtreenode.splitkey1)
446-
righttreenodenew = TreeNode{K}(newchild, oldtreenode.child3, 0,
447-
oldtreenode.parent,
448-
oldtreenode.splitkey2, oldtreenode.splitkey2)
449-
whichp = 2
422+
## change the 2-node to a 3-node with the new child.
423+
424+
while true
425+
426+
427+
# Let newchild1,...newchild4 be the new children of
428+
# the parent node
429+
# Initially, take the three children of the existing parent
430+
# node and set newchild4 to 0.
431+
432+
newchild1 = t.tree[p1].child1
433+
newchild2 = t.tree[p1].child2
434+
minkeychild2 = t.tree[p1].splitkey1
435+
newchild3 = t.tree[p1].child3
436+
minkeychild3 = t.tree[p1].splitkey2
437+
p1parent = t.tree[p1].parent
438+
newchild4 = 0
439+
440+
# Now figure out which of the 4 children is the new node
441+
# and insert it into newchild1 ... newchild4
442+
443+
if newchild1 == existingchild
444+
newchild4 = newchild3
445+
minkeychild4 = minkeychild3
446+
newchild3 = newchild2
447+
minkeychild3 = minkeychild2
448+
newchild2 = newchild
449+
minkeychild2 = minkeynewchild
450+
elseif newchild2 == existingchild
451+
newchild4 = newchild3
452+
minkeychild4 = minkeychild3
453+
newchild3 = newchild
454+
minkeychild3 = minkeynewchild
455+
elseif newchild3 == existingchild
456+
newchild4 = newchild
457+
minkeychild4 = minkeynewchild
450458
else
451-
lefttreenodenew = TreeNode{K}(oldtreenode.child1, oldtreenode.child2, 0,
452-
oldtreenode.parent,
453-
oldtreenode.splitkey1, oldtreenode.splitkey1)
454-
righttreenodenew = TreeNode{K}(oldtreenode.child3, newchild, 0,
455-
oldtreenode.parent,
456-
minkeynewchild, minkeynewchild)
457-
minkeynewchild = oldtreenode.splitkey2
458-
whichp = 2
459+
throw(AssertionError("Tree structure is corrupted 1"))
460+
end
461+
462+
# Two cases: either we need to split the tree node
463+
# if newchild4>0 else we convert a 2-node to a 3-node
464+
# if newchild4==0
465+
466+
if newchild4 == 0
467+
# Change the parent from a 2-node to a 3-node
468+
t.tree[p1] = TreeNode{K}(newchild1, newchild2, newchild3,
469+
p1parent, minkeychild2, minkeychild3)
470+
if curdepth == depth
471+
replaceparent!(t.data, newchild, p1)
472+
else
473+
replaceparent!(t.tree, newchild, p1)
474+
end
475+
break
459476
end
460-
# Replace p1 with a new 2-node and insert another 2-node at
461-
# index newparentnum.
462-
t.tree[p1] = lefttreenodenew
463-
newparentnum = push_or_reuse!(t.tree, t.freetreeinds, righttreenodenew)
464-
if isleaf
465-
par = (whichp == 1) ? p1 : newparentnum
466-
# fix the parent of the new datanode.
467-
replaceparent!(t.data, newind, par)
468-
push!(t.useddatacells, newind)
469-
replaceparent!(t.data, righttreenodenew.child1, newparentnum)
470-
replaceparent!(t.data, righttreenodenew.child2, newparentnum)
477+
# Split the parent
478+
t.tree[p1] = TreeNode{K}(newchild1, newchild2, 0,
479+
p1parent, minkeychild2, minkeychild2)
480+
newtreenode = TreeNode{K}(newchild3, newchild4, 0,
481+
p1parent, minkeychild4, minkeychild2)
482+
newparentnum = push_or_reuse!(t.tree, t.freetreeinds, newtreenode)
483+
if curdepth == depth
484+
replaceparent!(t.data, newchild2, p1)
485+
replaceparent!(t.data, newchild3, newparentnum)
486+
replaceparent!(t.data, newchild4, newparentnum)
471487
else
472-
# If this is not a leaf, we still have to fix the
473-
# parent fields of the two nodes that are now children
474-
## of the newparent.
475-
replaceparent!(t.tree, righttreenodenew.child1, newparentnum)
476-
replaceparent!(t.tree, righttreenodenew.child2, newparentnum)
488+
replaceparent!(t.tree, newchild2, p1)
489+
replaceparent!(t.tree, newchild3, newparentnum)
490+
replaceparent!(t.tree, newchild4, newparentnum)
477491
end
478-
oldchild = p1
492+
# Update the loop variables for the next level of the
493+
# ascension
494+
existingchild = p1
479495
newchild = newparentnum
480-
## If p1 is the root (i.e., we have encountered only 3-nodes during
481-
## our ascent of the tree), then the root must be split.
482-
if p1 == t.rootloc
483-
@invariant curdepth == 1
496+
p1 = p1parent
497+
minkeynewchild = minkeychild3
498+
curdepth -= 1
499+
if curdepth == 0
484500
splitroot = true
485501
break
486502
end
487-
p1 = t.tree[oldchild].parent
488-
curdepth -= 1
489503
end
490504

491-
## big loop terminated either because a 2-node was reached
492-
## (splitroot == false) or we went up the whole tree seeing
493-
## only 3-nodes (splitroot == true).
494-
if !splitroot
495-
496-
## If our ascent reached a 2-node, then we convert it to
497-
## a 3-node by giving it a child3 field that is >0.
498-
## Encountering a 2-node halts the ascent up the tree.
499-
500-
isleaf = curdepth == depth
501-
oldtreenode = t.tree[p1]
502-
cmpres = isleaf ? cmp2_leaf(ord, oldtreenode, minkeynewchild) :
503-
cmp2_nonleaf(ord, oldtreenode, minkeynewchild)
504-
505-
506-
t.tree[p1] = cmpres == 1 ?
507-
TreeNode{K}(oldtreenode.child1, newchild, oldtreenode.child2,
508-
oldtreenode.parent,
509-
minkeynewchild, oldtreenode.splitkey1) :
510-
TreeNode{K}(oldtreenode.child1, oldtreenode.child2, newchild,
511-
oldtreenode.parent,
512-
oldtreenode.splitkey1, minkeynewchild)
513-
if isleaf
514-
replaceparent!(t.data, newind, p1)
515-
push!(t.useddatacells, newind)
516-
end
517-
else
518-
## Splitroot is set if the ascent of the tree encountered only 3-nodes.
519-
## In this case, the root itself was replaced by two nodes, so we need
520-
## a new root above those two.
505+
# If the root has been split, then we need to add a level
506+
# to the tree that is the parent of the old root and the new node.
521507

522-
newroot = TreeNode{K}(oldchild, newchild, 0, 0,
523-
minkeynewchild, minkeynewchild)
508+
if splitroot
509+
@invariant existingchild == t.rootloc
510+
newroot = TreeNode{K}(existingchild, newchild, 0,
511+
0, minkeynewchild, minkeynewchild)
512+
524513
newrootloc = push_or_reuse!(t.tree, t.freetreeinds, newroot)
525-
replaceparent!(t.tree, oldchild, newrootloc)
514+
replaceparent!(t.tree, existingchild, newrootloc)
526515
replaceparent!(t.tree, newchild, newrootloc)
527516
t.rootloc = newrootloc
528517
t.depth += 1
@@ -615,7 +604,7 @@ end
615604
##, 1 if i2 precedes i1.
616605

617606
function compareInd(t::BalancedTree23, i1::Int, i2::Int)
618-
@assert(i1 in t.useddatacells && i2 in t.useddatacells)
607+
@invariant(i1 in t.useddatacells && i2 in t.useddatacells)
619608
if i1 == i2
620609
return 0
621610
end
@@ -624,6 +613,7 @@ function compareInd(t::BalancedTree23, i1::Int, i2::Int)
624613
p1 = t.data[i1].parent
625614
p2 = t.data[i2].parent
626615
@invariant_support_statement curdepth = t.depth
616+
curdepth = t.depth
627617
while true
628618
@invariant curdepth > 0
629619
if p1 == p2
@@ -647,6 +637,7 @@ function compareInd(t::BalancedTree23, i1::Int, i2::Int)
647637
p1 = t.tree[i1a].parent
648638
p2 = t.tree[i2a].parent
649639
@invariant_support_statement curdepth -= 1
640+
curdepth -= 1
650641
end
651642
end
652643

test/test_sorted_containers.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,32 @@ function testSortedMultiDict()
14031403
end
14041404
# issue #216
14051405
my_assert(DataStructures.isordered(SortedMultiDict{Int, String}))
1406+
# issue #773
1407+
s = SortedMultiDict{Int, Int}()
1408+
insert!(s, 4, 41)
1409+
insert!(s, 3, 31)
1410+
insert!(s, 2, 21)
1411+
insert!(s, 2, 22)
1412+
insert!(s, 2, 23)
1413+
insert!(s, 2, 24)
1414+
insert!(s, 2, 25)
1415+
insert!(s, 2, 26)
1416+
insert!(s, 1, 11)
1417+
insert!(s, 1, 12)
1418+
st1 = insert!(s, 1, 13)
1419+
st2 = insert!(s, 1, 14)
1420+
st3 = insert!(s, 1, 15)
1421+
st4 = insert!(s, 1, 16)
1422+
st5 = insert!(s, 1, 17)
1423+
st6 = insert!(s, 1, 18)
1424+
delete!((s, st6))
1425+
delete!((s, st5))
1426+
delete!((s, st4))
1427+
delete!((s, st3))
1428+
delete!((s, st2))
1429+
delete!((s, st1))
1430+
insert!(s, 1, 19)
1431+
checkcorrectness(s.bt, true)
14061432
true
14071433
end
14081434

0 commit comments

Comments
 (0)