nim-stew/stew/sorted_set/rbtree_delete.nim

139 lines
4.2 KiB
Nim

# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
import
./rbtree_desc,
./rbtree_rotate,
../results
{.push raises: [Defect].}
# ------------------------------------------------------------------------------
# Public
# ------------------------------------------------------------------------------
proc rbTreeDelete*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
## Generic red-black tree function, removes a node from the red-black tree.
## The node to be removed wraps a data container `casket` matching the
## argument `key`, i.e. `rbt.cmp(casket,key) == 0`.
##
## If the node was successfully deleted, the function returns the matching
## `casket` data container. Otherwise, there was no such node with a matching
## `casket` in the tree.
##
## :Ackn:
## `jsw_rberase()` from jsw_rbtree.c from captured C library
## `jsw_rbtree.zip <https://web.archive.org/web/20160428112900/http://eternallyconfuzzled.com/libs/jsw_rbtree.zip>`_.
##
if rbt.root.isNil:
return err(rbEmptyTree)
var
dir = rbRight # start here
found: RbNodeRef[C] # node to be removed (if any)
head = RbNodeRef[C](
link: [nil, rbt.root]) # black false tree root
# ancestry line: g -> p -> q
grandParent: RbNodeRef[C] # grandparent => NIL
parent: RbNodeRef[C] # parent => NIL
q = head # iterator
# Search and push a red node down to fix red violations as we go
while not q.link[dir].isNil:
# Move the helpers down
grandParent = parent
parent = q
q = q.link[dir]
let
last = dir
diff = rbt.cmp(q.casket,key)
dir = (diff < 0).toDir
# Save matching node and keep going, removal job is done below this loop
if diff == 0:
found = q
# Push the red node down with rotations and color flips
if q.isRed or q.link[dir].isRed:
continue
if q.link[not dir].isRed:
let qrs = q.rbTreeRotateSingle(dir)
parent.link[last] = qrs
parent = qrs
# Mark traversal path unusable
rbt.dirty = rbt.dirty or rbTreeReBalancedFlag
continue
# Now: not q.link[not dir].isRed
let sibling = parent.link[not last]
if sibling.isNil:
continue
# Note that `link.isRed` => `not link.isNil`
if not sibling.linkLeft.isRed and not sibling.linkRight.isRed:
# Color flip
parent.isRed = false # aka black
sibling.isRed = true
q.isRed = true
continue
let dir2 = (grandParent.linkRight == parent).toDir
if sibling.link[last].isRed:
grandParent.link[dir2] = parent.rbTreeRotateDouble(last)
rbt.dirty = rbt.dirty or rbTreeReBalancedFlag
elif sibling.link[not last].isRed:
grandParent.link[dir2] = parent.rbTreeRotateSingle(last)
rbt.dirty = rbt.dirty or rbTreeReBalancedFlag
# Ensure correct coloring
q.isRed = true
let ggp2 = grandParent.link[dir2]
ggp2.isRed = true
ggp2.linkLeft.isRed = false # aka black
ggp2.linkRight.isRed = false # aka black
# End while
# Replace and remove the saved node */
if found.isNil:
result = err(rbNotFound)
else:
result = ok(found.casket)
found.casket = q.casket
let
dirX = (parent.linkRight == q).toDir
dirY = q.linkLeft.isNil.toDir
parent.link[dirX] = q.link[dirY];
# clear node cache if this was the one to be deleted
if not rbt.cache.isNil and rbt.cmp(rbt.cache.casket,key) == 0:
rbt.cache = nil
q = nil # some hint for the GC to recycle that node
rbt.size.dec
rbt.dirty = rbt.dirty or rbTreeReBalancedFlag
# Update the root (it may be different)
rbt.root = head.linkRight
# Mark the root black for simplified logic
if not rbt.root.isNil:
rbt.root.isRed = false # aka black
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------