311 lines
11 KiB
Nim
311 lines
11 KiB
Nim
# Nimbus
|
||
# Copyright (c) 2018-2024 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.
|
||
|
||
## Generic Red-black Tree
|
||
## ======================
|
||
##
|
||
## This `red-black <https://en.wikipedia.org/wiki/Red–black_tree>`_ tree
|
||
## library was inspired by Julienne Walker's excellent tutorial,
|
||
## captured `here <https://archive.is/miDT>`_ or
|
||
## `here <https://web.archive.org/web/20180706105528/http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx>`_.
|
||
## The downloadable C library has been captured
|
||
## `here <https://web.archive.org/web/20160428112900/http://eternallyconfuzzled.com/libs/jsw_rbtree.zip>`_.
|
||
##
|
||
## In a nutshell,t a *red-black tree* emulates a *b-tree* by replacing a
|
||
## b-tree node
|
||
## ::
|
||
## | a, b, c
|
||
## | / | | \
|
||
##
|
||
## by red or black colored binary nodes
|
||
## ::
|
||
## | b
|
||
## | <black>
|
||
## | a / \ c
|
||
## | <red> <red>
|
||
## | / \ / \
|
||
##
|
||
## So, apart from insert and delete operations which are basically masked
|
||
## *b-tree* operations, search and traversal tools for binary trees can be
|
||
## used for *red-black trees* as well.
|
||
##
|
||
## Red-black tree module interface components
|
||
## ------------------------------------------
|
||
##
|
||
## :C:
|
||
## Opaque data type: It is a comparable contents or data container derived
|
||
## from a key data item of type `K` (see comments on `RbMkcFn` type.) This
|
||
## data type `C` must be an object *reference*.
|
||
##
|
||
## :K:
|
||
## Opaque index type: It is used to identify and retrieve some data
|
||
## container of type `C`.
|
||
##
|
||
|
||
# Historic ackn:
|
||
# http://eternallyconfuzzled.com/libs/jsw_rbtree.zip (now defunct)
|
||
#
|
||
# Original copyright notice from jsw_rbtree.h:
|
||
# > Created (Julienne Walker): August 23, 2003
|
||
# > Modified (Julienne Walker): March 14, 2008
|
||
#
|
||
# This code is in the public domain. Anyone may
|
||
# use it or change it in any way that they see
|
||
# fit. The author assumes no responsibility for
|
||
# damages incurred through use of the original
|
||
# code or any variations thereof.
|
||
#
|
||
# It is requested, but not required, that due
|
||
# credit is given to the original author and
|
||
# anyone who has modified the code through
|
||
# a header comment, such as this one.typedef
|
||
|
||
{.push raises: [].}
|
||
|
||
import
|
||
results,
|
||
std/[tables]
|
||
|
||
const
|
||
rbTreeReBalancedFlag* = 1
|
||
rbTreeNodesDeletedFlag* = 2
|
||
rbTreeFlushDataFlag* = 4
|
||
|
||
type
|
||
RbCmpFn*[C,K] = ##\
|
||
## A function of this type compares a `casket` argument against the `key` \
|
||
## argument.
|
||
## The function returns either zero, a positive, or a negaitve integer not
|
||
## unlike `cmp()` for integers or strings. This type of function is used
|
||
## for organising the red-black tree in a sorted manner.
|
||
proc(casket: C; key: K): int {.gcsafe.}
|
||
|
||
RbMkcFn*[C,K] = ##\
|
||
## A function of this type creates a new object `C` from argument key `K`.
|
||
## Given a pair of functions `(cmp,mkc)` of respective types
|
||
## `(RbCmpFn,RbMkcFn)`, the function `mkc` must satisfy
|
||
## ::
|
||
## cmp(mkc(key),key) == 0
|
||
##
|
||
## which is taken for granted and *not* verified by the red-black tree
|
||
## functions. Also, `mkc()` must be injective, i.e.
|
||
## ::
|
||
## key != key' => mkc(key) != mkc(key')
|
||
##
|
||
## Once generated, the value `mkc(key)` of type `C` will be made
|
||
## accessible by the API so that it can be modified but it must be made
|
||
## certain that no modification changes the reverse image of `mkc()`,
|
||
## i.e. for every modification `mod:C -> C` the following must hold
|
||
## ::
|
||
## cmp(mod(mkc(key)),key) == 0
|
||
##
|
||
## A trivial example for `mkc()` would be to return a copy of the argument
|
||
## key and consider it read-only.
|
||
proc(key: K): C {.gcsafe.}
|
||
|
||
RbInfo* = enum ##\
|
||
## Used as code error compinent in `RbResult` function return code.
|
||
rbOk = 0 ## Seldom used (mainly for debugging)
|
||
rbFail ## Just failed something
|
||
rbEmptyTree ## No data yet
|
||
rbNotFound ## No matching entry
|
||
rbExists ## Could not insert as new entry
|
||
|
||
rbEndOfWalk ## All nodes visited
|
||
rbWalkClosed ## This walk has been closed
|
||
rbWalkBlocked ## Tree changed while walking
|
||
|
||
rbVfyRootIsRed ## Debug: Root node is red
|
||
rbVfyRedParentRedLeftLink ## ..
|
||
rbVfyRedParentRedRightLink
|
||
rbVfyRedParentRedBothLinks
|
||
rbVfyLeftLinkGtParent
|
||
rbVfyRightLinkLtParent
|
||
rbVfyBothLinkCmpParentReversed
|
||
rbVfyBlackChainLevelMismatch
|
||
|
||
RbDir* = enum ##\
|
||
## Node link direction, array index.
|
||
## The red-black tree implementation here also requires implicit colour
|
||
## value encodings `false` for black and `true` for red (see getters
|
||
## `isRed()`, `toDir()`, `not()`, and the `isRed=()` setter.)
|
||
rbLeft = 0
|
||
rbRight = 1
|
||
|
||
RbResult*[C] = ##\
|
||
## Combined function return code, data value or errror code.
|
||
Result[C,RbInfo]
|
||
|
||
RbNodeRef*[C] = ref object ##\
|
||
## Node with value container, main component of a red-black tree.
|
||
## These nodes build up the red-black tree (see
|
||
## `eternally confuzzled <https://archive.is/miDT>`_.)
|
||
redColour: bool ## Algorithm dependent colour marker
|
||
link*:array[RbDir,RbNodeRef[C]] ## Left and right tree links, vertex
|
||
casket*: C ## Comparable node data container
|
||
|
||
RbTreeRef*[C,K] = ref object of RootObj ##\
|
||
## Red-black tree descriptor object
|
||
cmpFn: RbCmpFn[C,K] ## Comparison handler
|
||
mkcFn: RbMkcFn[C,K] ## Pseudo-dup handler
|
||
root*: RbNodeRef[C] ## Top of the tree
|
||
cache*: RbNodeRef[C] ## Last node created, found etc.
|
||
size*: int ## Number of node items
|
||
dirty*: int ## Reset walk while tree is manipulated
|
||
walkIdGen: uint ## Id generaror for walks[] table
|
||
walks*: Table[uint,RbWalkRef[C,K]] ## Open walk descriptors list
|
||
|
||
RbWalkRef*[C,K] = ref object of RootObj ##\
|
||
## Traversal descriptor for a red-black tree
|
||
id*: uint ## walks[] table registry
|
||
tree*: RbTreeRef[C,K] ## Paired tree
|
||
node*: RbNodeRef[C] ## Current node
|
||
path*: seq[RbNodeRef[C]] ## Traversal path
|
||
top*: int ## Top of stack
|
||
start*: bool ## `true` after a rewind operation
|
||
stop*: bool ## End of traversal
|
||
|
||
{.push raises: [].}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Public functions, constructor
|
||
# ------------------------------------------------------------------------------
|
||
|
||
func newRbTreeRef*[C,K](cmp: RbCmpFn[C,K]; mkc: RbMkcFn[C,K]): RbTreeRef[C,K] =
|
||
## Constructor. Create generic red-black tree descriptor for data container
|
||
## type `C` and key type `K`. Details about the function arguments `cmpFn`
|
||
## and `mkcFn` are documented with the type definitions of `RbCmpFn` and
|
||
## `RbMkcFn`.
|
||
RbTreeRef[C,K](
|
||
cmpFn: cmp,
|
||
mkcFn: mkc,
|
||
walkIdGen: 1, # next free ID
|
||
walks: initTable[uint,RbWalkRef[C,K]](1))
|
||
|
||
|
||
func newWalkId*[C,K](rbt: RbTreeRef[C,K]): uint {.inline.} =
|
||
## Generate new free walk ID, returns zero in (theoretical) case all other
|
||
## IDs are exhausted.
|
||
for id in rbt.walkIdGen .. high(type rbt.walkIdGen):
|
||
if not rbt.walks.hasKey(id):
|
||
rbt.walkIdGen = id
|
||
return id
|
||
for id in 1u ..< rbt.walkIdGen:
|
||
if not rbt.walks.hasKey(id):
|
||
rbt.walkIdGen = id
|
||
return id
|
||
0
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Public handlers
|
||
# ------------------------------------------------------------------------------
|
||
|
||
func cmp*[C,K](rbt: RbTreeRef[C,K]; casket: C; key: K): int {.inline.} =
|
||
## See introduction for an explanation of opaque argument types `C` and `D`,
|
||
## and the type definition for `RbCmpFn` for properties of this function.
|
||
rbt.cmpFn(casket, key)
|
||
|
||
func mkc*[C,K](rbt: RbTreeRef[C,K]; key: K): C {.inline.} =
|
||
## See introduction for an explanation of opaque argument/return types `C`
|
||
## and `D`, and the type definition for `RbMkdFn` for properties of this
|
||
## function.
|
||
rbt.mkcFn(key)
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Public getters
|
||
# ------------------------------------------------------------------------------
|
||
|
||
func linkLeft*[C](node: RbNodeRef[C]): RbNodeRef[C] {.inline.} =
|
||
## Getter, shortcut for `node.link[rbLeft]`
|
||
node.link[rbLeft]
|
||
|
||
func linkRight*[C](node: RbNodeRef[C]): RbNodeRef[C] {.inline.} =
|
||
## Getter, shortcut for `node.link[rbRight]`
|
||
node.link[rbRight]
|
||
|
||
func isRed*[C](node: RbNodeRef[C]): bool {.inline.} =
|
||
## Getter, `true` if node colour is read.
|
||
not node.isNil and node.redColour
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Public setters
|
||
# ------------------------------------------------------------------------------
|
||
|
||
func `linkLeft=`*[C](node, child: RbNodeRef[C]) {.inline.} =
|
||
## Getter, shortcut for `node.link[rbLeft] = child`
|
||
node.link[rbLeft] = child
|
||
|
||
func `linkRight=`*[C](node, child: RbNodeRef[C]) {.inline.} =
|
||
## Getter, shortcut for `node.link[rbRight] = child`
|
||
node.link[rbRight] = child
|
||
|
||
func `isRed=`*[C](node: RbNodeRef[C]; value: bool) {.inline.} =
|
||
## Setter, `true` sets red node colour.
|
||
node.redColour = value
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Public helpers: `rbDir` as array index
|
||
# ------------------------------------------------------------------------------
|
||
|
||
func `not`*(d: RbDir): RbDir {.inline.} =
|
||
## Opposite direction of argument `d`.
|
||
if d == rbLeft: rbRight else: rbLeft
|
||
|
||
func toDir*(b: bool): RbDir {.inline.} =
|
||
## Convert to link diection `rbLeft` (false) or `rbRight` (true).
|
||
if b: rbRight else: rbLeft
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Public pretty printer
|
||
# ------------------------------------------------------------------------------
|
||
|
||
func `$`*[C](node: RbNodeRef[C]): string =
|
||
## Pretty printer, requres `$()` for type `C` to be known.
|
||
if node.isNil:
|
||
return "nil"
|
||
proc colour(red: bool): string =
|
||
if red: "red" else: "black"
|
||
"(" &
|
||
node.isRed.colour & "," &
|
||
$node.casket & "," &
|
||
"left=" & $node.linkLeft & "," &
|
||
"right=" & $node.linkRight & ")"
|
||
|
||
func `$`*[C,K](rbt: RbTreeRef[C,K]): string =
|
||
## Pretty printer
|
||
if rbt.isNil:
|
||
return "nil"
|
||
"[" &
|
||
"size=" & $rbt.size & "," &
|
||
"gen=" & $rbt.walkIdGen & "," &
|
||
"root=" & $rbt.root & "]"
|
||
|
||
func `$`*[C,K](w: RbWalkRef[C,K]): string =
|
||
## Pretty printer
|
||
if w.isNil:
|
||
return "nil"
|
||
result = "[id=" & $w.id
|
||
if w.tree.isNil:
|
||
result &= ",tree=nil"
|
||
if w.node.isNil:
|
||
result &= ",node=nil"
|
||
else:
|
||
result &= ",node=" & $w.node.casket
|
||
result &= ",path.len=" & $w.path.len
|
||
if w.start:
|
||
result &= ",start"
|
||
if w.stop:
|
||
result &= ",stop"
|
||
result &= "]"
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# End
|
||
# ------------------------------------------------------------------------------
|