New data structures - SortedSet and KeyedQueue
See the modules' documentation for more details
This commit is contained in:
parent
478cc6efde
commit
b002c1fad0
|
@ -83,10 +83,10 @@ jobs:
|
|||
run: |
|
||||
mkdir -p external
|
||||
if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then
|
||||
MINGW_URL="https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win64/Personal Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z"
|
||||
MINGW_URL="https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z"
|
||||
ARCH=64
|
||||
else
|
||||
MINGW_URL="https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win32/Personal Builds/mingw-builds/8.1.0/threads-posix/dwarf/i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z"
|
||||
MINGW_URL="https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/8.1.0/threads-posix/dwarf/i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z"
|
||||
ARCH=32
|
||||
fi
|
||||
curl -L "$MINGW_URL" -o "external/mingw-${{ matrix.target.cpu }}.7z"
|
||||
|
|
|
@ -0,0 +1,885 @@
|
|||
# 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.
|
||||
|
||||
## Keyed Queue
|
||||
## ===========
|
||||
##
|
||||
## This module provides a keyed fifo or stack data structure similar to
|
||||
## `DoublyLinkedList` but with efficient random data access for fetching
|
||||
## and deletion. The underlying data structure is a hash table with data
|
||||
## lookup and delete assumed to be O(1) in most cases (so long as the
|
||||
## underlying hash table does not degrade into one-bucket linear mode, or
|
||||
## some bucket-adjustment algorithm takes over.)
|
||||
##
|
||||
## For consistency with other data types in Nim the queue has value
|
||||
## semantics, this means that `=` performs a deep copy of the allocated queue
|
||||
## which is refered to the deep copy semantics of the underlying table driver.
|
||||
|
||||
import
|
||||
std/[math, tables],
|
||||
./results
|
||||
|
||||
export
|
||||
results
|
||||
|
||||
type
|
||||
KeyedQueueItem*[K,V] = object ##\
|
||||
## Data value container as stored in the queue.
|
||||
## There is a special requirements for `KeyedQueueItem` terminal nodes:
|
||||
## *prv == nxt* so that there is no dangling link. On the flip side,
|
||||
## this requires some extra consideration when deleting the second node
|
||||
## relative to either end.
|
||||
data*: V ## Some data value, can freely be modified.
|
||||
kPrv*, kNxt*: K ## Queue links, read-only.
|
||||
|
||||
KeyedQueuePair*[K,V] = object ##\
|
||||
## Key-value pair, typically used as return code.
|
||||
key: K ## Sorter key (read-only for consistency with `SLstResult[K,V]`)
|
||||
data*: V ## Some data value, to be modified freely
|
||||
|
||||
KeyedQueueTab*[K,V] = ##\
|
||||
## Internal table type exposed for debugging.
|
||||
Table[K,KeyedQueueItem[K,V]]
|
||||
|
||||
KeyedQueue*[K,V] = object ##\
|
||||
## Data queue descriptor
|
||||
tab*: KeyedQueueTab[K,V] ## Data table
|
||||
kFirst*, kLast*: K ## Doubly linked item list queue
|
||||
|
||||
BlindValue = ##\
|
||||
## Type name is syntactic sugar, used for key-only queues
|
||||
distinct byte
|
||||
|
||||
KeyedQueueNV*[K] = ##\
|
||||
## Key-only queue, no values
|
||||
KeyedQueue[K,BlindValue]
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc shiftImpl[K,V](rq: var KeyedQueue[K,V])
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Expects: rq.tab.len != 0
|
||||
|
||||
# Unqueue first item
|
||||
let item = rq.tab[rq.kFirst] # yes, crashes if `rq.tab.len == 0`
|
||||
rq.tab.del(rq.kFirst)
|
||||
|
||||
if rq.tab.len == 0:
|
||||
rq.kFirst.reset
|
||||
rq.kLast.reset
|
||||
else:
|
||||
rq.kFirst = item.kNxt
|
||||
if rq.tab.len == 1:
|
||||
rq.tab[rq.kFirst].kNxt = rq.kFirst # node points to itself
|
||||
rq.tab[rq.kFirst].kPrv = rq.tab[rq.kFirst].kNxt # term node has: nxt == prv
|
||||
|
||||
|
||||
proc popImpl[K,V](rq: var KeyedQueue[K,V])
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Expects: rq.tab.len != 0
|
||||
|
||||
# Pop last item
|
||||
let item = rq.tab[rq.kLast] # yes, crashes if `rq.tab.len == 0`
|
||||
rq.tab.del(rq.kLast)
|
||||
|
||||
if rq.tab.len == 0:
|
||||
rq.kFirst.reset
|
||||
rq.kLast.reset
|
||||
else:
|
||||
rq.kLast = item.kPrv
|
||||
if rq.tab.len == 1:
|
||||
rq.tab[rq.kLast].kPrv = rq.kLast # single node points to itself
|
||||
rq.tab[rq.kLast].kNxt = rq.tab[rq.kLast].kPrv # term node has: nxt == prv
|
||||
|
||||
|
||||
proc deleteImpl[K,V](rq: var KeyedQueue[K,V]; key: K)
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Expects: rq.tab.hesKey(key)
|
||||
|
||||
if rq.kFirst == key:
|
||||
rq.shiftImpl
|
||||
|
||||
elif rq.kLast == key:
|
||||
rq.popImpl
|
||||
|
||||
else:
|
||||
let item = rq.tab[key] # yes, crashes if `not rq.tab.hasKey(key)`
|
||||
rq.tab.del(key)
|
||||
|
||||
# now: 2 < rq.tab.len (otherwise rq.kFirst == key or rq.kLast == key)
|
||||
if rq.tab[rq.kFirst].kNxt == key:
|
||||
# item was the second one
|
||||
rq.tab[rq.kFirst].kPrv = item.kNxt
|
||||
if rq.tab[rq.kLast].kPrv == key:
|
||||
# item was one before last
|
||||
rq.tab[rq.kLast].kNxt = item.kPrv
|
||||
|
||||
rq.tab[item.kPrv].kNxt = item.kNxt
|
||||
rq.tab[item.kNxt].kPrv = item.kPrv
|
||||
|
||||
|
||||
proc appendImpl[K,V](rq: var KeyedQueue[K,V]; key: K; val: V)
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Expects: not rq.tab.hasKey(key)
|
||||
|
||||
# Append queue item
|
||||
var item = KeyedQueueItem[K,V](data: val)
|
||||
|
||||
if rq.tab.len == 0:
|
||||
rq.kFirst = key
|
||||
item.kPrv = key
|
||||
else:
|
||||
if rq.kFirst == rq.kLast:
|
||||
rq.tab[rq.kFirst].kPrv = key # first terminal node
|
||||
rq.tab[rq.kLast].kNxt = key
|
||||
item.kPrv = rq.kLast
|
||||
|
||||
rq.kLast = key
|
||||
item.kNxt = item.kPrv # terminal node
|
||||
|
||||
rq.tab[key] = item # yes, makes `verify()` fail if `rq.tab.hasKey(key)`
|
||||
|
||||
|
||||
proc prependImpl[K,V](rq: var KeyedQueue[K,V]; key: K; val: V)
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Expects: not rq.tab.hasKey(key)
|
||||
|
||||
# Prepend queue item
|
||||
var item = KeyedQueueItem[K,V](data: val)
|
||||
|
||||
if rq.tab.len == 0:
|
||||
rq.kLast = key
|
||||
item.kNxt = key
|
||||
else:
|
||||
if rq.kFirst == rq.kLast:
|
||||
rq.tab[rq.kLast].kNxt = key # first terminal node
|
||||
rq.tab[rq.kFirst].kPrv = key
|
||||
item.kNxt = rq.kFirst
|
||||
|
||||
rq.kFirst = key
|
||||
item.kPrv = item.kNxt # terminal node has: nxt == prv
|
||||
|
||||
rq.tab[key] = item # yes, makes `verify()` fail if `rq.tab.hasKey(key)`
|
||||
|
||||
# -----------
|
||||
|
||||
proc shiftKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
if 0 < rq.tab.len:
|
||||
let key = rq.kFirst
|
||||
rq.shiftImpl
|
||||
return ok(key)
|
||||
err()
|
||||
|
||||
proc popKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
if 0 < rq.tab.len:
|
||||
let key = rq.kLast
|
||||
rq.popImpl
|
||||
return ok(key)
|
||||
err()
|
||||
|
||||
# -----------
|
||||
|
||||
proc firstKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] =
|
||||
if rq.tab.len == 0:
|
||||
return err()
|
||||
ok(rq.kFirst)
|
||||
|
||||
proc secondKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
if rq.tab.len < 2:
|
||||
return err()
|
||||
ok(rq.tab[rq.kFirst].kNxt)
|
||||
|
||||
proc beforeLastKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
if rq.tab.len < 2:
|
||||
return err()
|
||||
ok(rq.tab[rq.kLast].kPrv)
|
||||
|
||||
proc lastKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] =
|
||||
if rq.tab.len == 0:
|
||||
return err()
|
||||
ok(rq.kLast)
|
||||
|
||||
proc nextKeyImpl[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
if not rq.tab.hasKey(key) or rq.kLast == key:
|
||||
return err()
|
||||
ok(rq.tab[key].kNxt)
|
||||
|
||||
proc prevKeyImpl[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
if not rq.tab.hasKey(key) or rq.kFirst == key:
|
||||
return err()
|
||||
ok(rq.tab[key].kPrv)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc init*[K,V](rq: var KeyedQueue[K,V]; initSize = 10) =
|
||||
## Optional initaliser for the queue setting the inital size of the
|
||||
## underlying table object.
|
||||
rq.tab = initTable[K,KeyedQueueItem[K,V]](initSize.nextPowerOfTwo)
|
||||
|
||||
proc init*[K,V](T: type KeyedQueue[K,V]; initSize = 10): T =
|
||||
## Initaliser variant.
|
||||
result.init(initSize)
|
||||
|
||||
proc init*[K](rq: var KeyedQueueNV[K]; initSize = 10) =
|
||||
## Key-only queue, no explicit values
|
||||
rq.tab = initTable[K,KeyedQueueItem[K,BlindValue]](initSize.nextPowerOfTwo)
|
||||
|
||||
proc init*[K](T: type KeyedQueueNV[K]; initSize = 10): T =
|
||||
## Initaliser variant.
|
||||
result.init(initSize)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, list operations
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc append*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Append new `key`. The function will succeed returning `true` unless the
|
||||
## `key` argument exists in the queue, already.
|
||||
##
|
||||
## All the items on the queue different from the one just added are
|
||||
## called *previous* or *left hand* items while the item just added
|
||||
## is the *right-most* item.
|
||||
if not rq.tab.hasKey(key):
|
||||
rq.appendImpl(key, val)
|
||||
return true
|
||||
|
||||
template push*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool =
|
||||
## Same as `append()`
|
||||
rq.append(key, val)
|
||||
|
||||
|
||||
proc replace*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Replace value for entry associated with the key argument `key`. Returns
|
||||
## `true` on success, and `false` otherwise.
|
||||
if rq.tab.hasKey(key):
|
||||
rq.tab[key].data = val
|
||||
return true
|
||||
|
||||
proc `[]=`*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V)
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## This function provides a combined append/replace action with table
|
||||
## semantics:
|
||||
## * If the argument `key` is not in the queue yet, append the `(key,val)`
|
||||
## pair as in `rq.append(key,val)`
|
||||
## * Otherwise replace the value entry of the queue item by the argument
|
||||
## `val` as in `rq.replace(key,val)`
|
||||
if rq.tab.hasKey(key):
|
||||
rq.tab[key].data = val
|
||||
else:
|
||||
rq.appendImpl(key, val)
|
||||
|
||||
|
||||
proc prepend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Prepend new `key`. The function will succeed returning `true` unless the
|
||||
## `key` argument exists in the queue, already.
|
||||
##
|
||||
## All the items on the queue different from the item just added are
|
||||
## called *following* or *right hand* items while the item just added
|
||||
## is the *left-most* item.
|
||||
if not rq.tab.hasKey(key):
|
||||
rq.prependImpl(key, val)
|
||||
return true
|
||||
|
||||
template unshift*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool =
|
||||
## Same as `prepend()`
|
||||
rq.prepend(key,val)
|
||||
|
||||
|
||||
proc shift*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Deletes the *first* queue item and returns the key-value item pair just
|
||||
## deleted. For a non-empty queue this function is the same as
|
||||
## `rq.firstKey.value.delele`.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## item returned and deleted is the *left-most* item.
|
||||
if 0 < rq.tab.len:
|
||||
let kvp = KeyedQueuePair[K,V](
|
||||
key: rq.kFirst,
|
||||
data: rq.tab[rq.kFirst].data)
|
||||
rq.shiftImpl
|
||||
return ok(KeyedQueuePair[K,V](kvp))
|
||||
err()
|
||||
|
||||
proc shiftKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `shift()` but with different return value.
|
||||
rq.shiftKeyImpl
|
||||
|
||||
proc shiftValue*[K,V](rq: var KeyedQueue[K,V]):
|
||||
Result[V,void] {.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `shift()` but with different return value.
|
||||
if 0 < rq.tab.len:
|
||||
let val = rq.tab[rq.kFirst].data
|
||||
rq.shiftImpl
|
||||
return ok(val)
|
||||
err()
|
||||
|
||||
|
||||
proc pop*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Deletes the *last* queue item and returns the key-value item pair just
|
||||
## deleted. For a non-empty queue this function is the same as
|
||||
## `rq.lastKey.value.delele`.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## item returned and deleted is the *right-most* item.
|
||||
if 0 < rq.tab.len:
|
||||
let kvp = KeyedQueuePair[K,V](
|
||||
key: rq.kLast,
|
||||
data: rq.tab[rq.kLast].data)
|
||||
rq.popImpl
|
||||
return ok(KeyedQueuePair[K,V](kvp))
|
||||
err()
|
||||
|
||||
proc popKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `pop()` but with different return value.
|
||||
rq.popKeyImpl
|
||||
|
||||
proc popValue*[K,V](rq: var KeyedQueue[K,V]):
|
||||
Result[V,void] {.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `pop()` but with different return value.
|
||||
if 0 < rq.tab.len:
|
||||
let val = rq.tab[rq.kLast].data
|
||||
rq.popImpl
|
||||
return ok(val)
|
||||
err()
|
||||
|
||||
|
||||
proc delete*[K,V](rq: var KeyedQueue[K,V]; key: K):
|
||||
Result[KeyedQueuePair[K,V],void] {.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Delete the item with key `key` from the queue and returns the key-value
|
||||
## item pair just deleted (if any).
|
||||
if rq.tab.hasKey(key):
|
||||
let kvp = KeyedQueuePair[K,V](
|
||||
key: key,
|
||||
data: rq.tab[key].data)
|
||||
rq.deleteImpl(key)
|
||||
return ok(kvp)
|
||||
err()
|
||||
|
||||
proc del*[K,V](rq: var KeyedQueue[K,V]; key: K)
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `delete()` but without return code.
|
||||
if rq.tab.hasKey(key):
|
||||
rq.deleteImpl(key)
|
||||
|
||||
# --------
|
||||
|
||||
proc append*[K](rq: var KeyedQueueNV[K]; key: K): bool
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.append(key,BlindValue(0))
|
||||
|
||||
template push*[K](rq: var KeyedQueueNV[K]; key: K): bool =
|
||||
## Key-only queue variant
|
||||
rq.append(key)
|
||||
|
||||
|
||||
proc prepend*[K](rq: var KeyedQueueNV[K]; key: K): bool
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.prepend(key,BlindValue(0))
|
||||
|
||||
template unshift*[K](rq: var KeyedQueueNV[K]; key: K): bool =
|
||||
## Key-only queue variant
|
||||
rq.prepend(key)
|
||||
|
||||
|
||||
proc shift*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.shiftKeyImpl
|
||||
|
||||
proc shiftKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use shift() for key-only queue",
|
||||
raises: [Defect,KeyError].} =
|
||||
rq.shiftKeyImpl
|
||||
|
||||
|
||||
proc pop*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only variant of `pop()` (same as `popKey()`)
|
||||
rq.popKeyImpl
|
||||
|
||||
proc popKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use pop() for key-only queue",
|
||||
raises: [Defect,KeyError].} =
|
||||
rq.popKeyImpl
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, fetch
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc hasKey*[K,V](rq: var KeyedQueue[K,V]; key: K): bool =
|
||||
## Check whether the argument `key` has been queued, already
|
||||
rq.tab.hasKey(key)
|
||||
|
||||
|
||||
proc eq*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[V,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the value data stored with the argument `key` from
|
||||
## the queue if there is any.
|
||||
if not rq.tab.hasKey(key):
|
||||
return err()
|
||||
ok(rq.tab[key].data)
|
||||
|
||||
proc `[]`*[K,V](rq: var KeyedQueue[K,V]; key: K): V
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## This function provides a simplified version of the `eq()` function with
|
||||
## table semantics. Note that this finction throws a `KeyError` exception
|
||||
## unless the argument `key` exists in the queue.
|
||||
rq.tab[key].data
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, LRU mode
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc lruFetch*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[V,void]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
## Fetch in *last-recently-used* mode: If the argument `key` exists in the
|
||||
## queue, move the key-value item pair to the *right end* (see `append()`)
|
||||
## of the queue and return the value associated with the key.
|
||||
let rc = rq.delete(key)
|
||||
if rc.isErr:
|
||||
return err()
|
||||
# Unlink and re-append item
|
||||
rq.appendImpl(key, rc.value.data)
|
||||
ok(rc.value.data)
|
||||
|
||||
proc lruAppend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V; maxItems: int): V
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
## Append in *last-recently-used* mode: If the queue has at least `maxItems`
|
||||
## item entries, do `shift()` out the *left-most* one. Then `append()` the
|
||||
## key-value argument pair `(key,val)` to the *right end*. Together with
|
||||
## `lruFetch()` this function can be used to build a *LRU cache*:
|
||||
## ::
|
||||
## const queueMax = 10
|
||||
##
|
||||
## proc expensiveCalculation(key: int): Result[int,void] =
|
||||
## ...
|
||||
##
|
||||
## proc lruCache(q: var KeyedQueue[int,int]; key: int): Result[int,void] =
|
||||
## block:
|
||||
## let rc = q.lruFetch(key)
|
||||
## if rc.isOK:
|
||||
## return ok(rc.value)
|
||||
## block:
|
||||
## let rc = expensiveCalculation(key)
|
||||
## if rc.isOK:
|
||||
## return ok(q.lruAppend(key, rc.value, queueMax))
|
||||
## err()
|
||||
##
|
||||
# Limit number of cached items
|
||||
if maxItems <= rq.tab.len:
|
||||
rq.shiftImpl
|
||||
# Append new value
|
||||
rq.appendImpl(key, val)
|
||||
val
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public traversal functions, fetch keys
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc firstKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.inline,gcsafe.} =
|
||||
## Retrieve first key from the queue unless it is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## key returned is the *left-most* one.
|
||||
rq.firstKeyImpl
|
||||
|
||||
proc secondKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the key next after the first key from queue unless it is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## key returned is the one ti the right of the *left-most* one.
|
||||
rq.secondKeyImpl
|
||||
|
||||
proc beforeLastKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the key just before the last one from queue unless it is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## key returned is the one to the left of the *right-most* one.
|
||||
rq.beforeLastKeyImpl
|
||||
|
||||
proc lastKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void]
|
||||
{.inline,gcsafe.} =
|
||||
## Retrieve last key from queue unless it is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## key returned is the *right-most* one.
|
||||
rq.lastKeyImpl
|
||||
|
||||
proc nextKey*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the key following the argument `key` from queue if
|
||||
## there is any.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## key returned is the next one to the *right*.
|
||||
rq.nextKeyImpl(key)
|
||||
|
||||
proc prevKey*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the key preceeding the argument `key` from queue if
|
||||
## there is any.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## key returned is the next one to the *left*.
|
||||
rq.prevKeyImpl(key)
|
||||
|
||||
# ----------
|
||||
|
||||
proc firstKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use first() for key-only queue".} =
|
||||
rq.firstKeyImpl
|
||||
|
||||
proc secondKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use second() for key-only queue",
|
||||
raises: [Defect,KeyError].} =
|
||||
rq.secondKeyImpl
|
||||
|
||||
proc beforeLastKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use beforeLast() for key-only queue",
|
||||
raises: [Defect,KeyError].} =
|
||||
rq.beforeLastKeyImpl
|
||||
|
||||
proc lastKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use last() for key-only queue".} =
|
||||
rq.lastKeyImpl
|
||||
|
||||
proc nextKey*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use next() for key-only queue",
|
||||
raises: [Defect,KeyError].} =
|
||||
rq.nextKeyImpl(key)
|
||||
|
||||
proc prevKey*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void]
|
||||
{.inline,gcsafe,
|
||||
deprecated: "use prev() for key-only queue",
|
||||
raises: [Defect,KeyError].} =
|
||||
rq.nextKeyImpl(key)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public traversal functions, fetch key/value pairs
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc first*[K,V](rq: var KeyedQueue[K,V]):
|
||||
Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `firstKey()` but with key-value item pair return value.
|
||||
if rq.tab.len == 0:
|
||||
return err()
|
||||
let key = rq.kFirst
|
||||
ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data))
|
||||
|
||||
proc second*[K,V](rq: var KeyedQueue[K,V]):
|
||||
Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `secondKey()` but with key-value item pair return value.
|
||||
if rq.tab.len < 2:
|
||||
return err()
|
||||
let key = rq.tab[rq.kFirst].kNxt
|
||||
ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data))
|
||||
|
||||
proc beforeLast*[K,V](rq: var KeyedQueue[K,V]):
|
||||
Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `beforeLastKey()` but with key-value item pair return value.
|
||||
if rq.tab.len < 2:
|
||||
return err()
|
||||
let key = rq.tab[rq.kLast].kPrv
|
||||
ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data))
|
||||
|
||||
proc last*[K,V](rq: var KeyedQueue[K,V]):
|
||||
Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `lastKey()` but with key-value item pair return value.
|
||||
if rq.tab.len == 0:
|
||||
return err()
|
||||
let key = rq.kLast
|
||||
ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data))
|
||||
|
||||
proc next*[K,V](rq: var KeyedQueue[K,V]; key: K):
|
||||
Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `nextKey()` but with key-value item pair return value.
|
||||
if not rq.tab.hasKey(key) or rq.kLast == key:
|
||||
return err()
|
||||
let nKey = rq.tab[key].kNxt
|
||||
ok(KeyedQueuePair[K,V](key: nKey, data: rq.tab[nKey].data))
|
||||
|
||||
proc prev*[K,V](rq: var KeyedQueue[K,V]; key: K):
|
||||
Result[KeyedQueuePair[K,V],void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Similar to `prevKey()` but with key-value item pair return value.
|
||||
if not rq.tab.hasKey(key) or rq.kFirst == key:
|
||||
return err()
|
||||
let pKey = rq.tab[key].kPrv
|
||||
ok(KeyedQueuePair[K,V](key: pKey, data: rq.tab[pKey].data))
|
||||
|
||||
# ------------
|
||||
|
||||
proc first*[K](rq: var KeyedQueueNV[K]): Result[K,void] {.inline,gcsafe.} =
|
||||
## Key-only queue variant
|
||||
rq.firstKeyImpl
|
||||
|
||||
proc second*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.secondKeyImpl
|
||||
|
||||
proc beforeLast*[K](rq: var KeyedQueueNV[K]): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.beforeLastKeyImpl
|
||||
|
||||
proc last*[K](rq: var KeyedQueueNV[K]): Result[K,void] {.inline,gcsafe.} =
|
||||
## Key-only queue variant
|
||||
rq.lastKeyImpl
|
||||
|
||||
proc next*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.nextKeyImpl(key)
|
||||
|
||||
proc prev*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void]
|
||||
{.inline,gcsafe,raises: [Defect,KeyError].} =
|
||||
## Key-only queue variant
|
||||
rq.nextKeyImpl(key)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public traversal functions, data container items
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc firstValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve first value item from the queue unless it is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## value item returned is the *left-most* one.
|
||||
if rq.tab.len == 0:
|
||||
return err()
|
||||
ok(rq.tab[rq.kFirst].data)
|
||||
|
||||
proc secondValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the value item next to the first one from the queue unless it
|
||||
## is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## value item returned is the one to the *right* of the *left-most* one.
|
||||
if rq.tab.len < 2:
|
||||
return err()
|
||||
ok(rq.tab[rq.tab[rq.kFirst].kNxt].data)
|
||||
|
||||
proc beforeLastValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the value item just before the last item from the queue
|
||||
## unless it is empty.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## value item returned is the one to the *left* of the *right-most* one.
|
||||
if rq.tab.len < 2:
|
||||
return err()
|
||||
ok(rq.tab[rq.tab[rq.kLast].kPrv].data)
|
||||
|
||||
proc lastValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Retrieve the last value item from the queue if there is any.
|
||||
##
|
||||
## Using the notation introduced with `rq.append` and `rq.prepend`, the
|
||||
## value item returned is the *right-most* one.
|
||||
if rq.tab.len == 0:
|
||||
return err()
|
||||
ok(rq.tab[rq.kLast].data)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, miscellaneous
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `==`*[K,V](a, b: var KeyedQueue[K,V]): bool
|
||||
{.gcsafe, raises: [Defect,KeyError].} =
|
||||
## Returns `true` if both argument queues contain the same data. Note that
|
||||
## this is a slow operation as all `(key,data)` pairs will to be compared.
|
||||
if a.tab.len == b.tab.len and a.kFirst == b.kFirst and a.kLast == b.kLast:
|
||||
for (k,av) in a.tab.pairs:
|
||||
if not b.tab.hasKey(k):
|
||||
return false
|
||||
let bv = b.tab[k]
|
||||
# bv.data might be a reference, so dive into it explicitely.
|
||||
if av.kPrv != bv.kPrv or av.kNxt != bv.kNxt or bv.data != av.data:
|
||||
return false
|
||||
return true
|
||||
|
||||
proc key*[K,V](kqp: KeyedQueuePair[K,V]): K {.inline.} =
|
||||
## Getter
|
||||
kqp.key
|
||||
|
||||
proc len*[K,V](rq: var KeyedQueue[K,V]): int {.inline.} =
|
||||
## Returns the number of items in the queue
|
||||
rq.tab.len
|
||||
|
||||
proc clear*[K,V](rq: var KeyedQueue[K,V]) {.inline.} =
|
||||
## Clear the queue
|
||||
rq.tab.clear
|
||||
rq.kFirst.reset
|
||||
rq.kLast.reset
|
||||
|
||||
proc toKeyedQueueResult*[K,V](key: K; data: V):
|
||||
Result[KeyedQueuePair[K,V],void] =
|
||||
## Helper, chreate `ok()` result
|
||||
ok(KeyedQueuePair[K,V](key: key, data: data))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public iterators
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
iterator nextKeys*[K,V](rq: var KeyedQueue[K,V]): K
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Iterate over all keys in the queue starting with the `rq.firstKey.value`
|
||||
## key (if any). Using the notation introduced with `rq.append` and
|
||||
## `rq.prepend`, the iterator processes *left* to *right*.
|
||||
##
|
||||
## :Note:
|
||||
## When running in a loop it is *ok* to delete the current item and all
|
||||
## the items already visited. Items not visited yet must not be deleted
|
||||
## as the loop would be come unpredictable, then.
|
||||
if 0 < rq.tab.len:
|
||||
var
|
||||
key = rq.kFirst
|
||||
loopOK = true
|
||||
while loopOK:
|
||||
let yKey = key
|
||||
loopOK = key != rq.kLast
|
||||
key = rq.tab[key].kNxt
|
||||
yield yKey
|
||||
|
||||
iterator nextValues*[K,V](rq: var KeyedQueue[K,V]): V
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Iterate over all values in the queue starting with the
|
||||
## `rq.kFirst.value.value` item value (if any). Using the notation introduced
|
||||
## with `rq.append` and `rq.prepend`, the iterator processes *left* to
|
||||
## *right*.
|
||||
##
|
||||
## See the note at the `nextKeys()` function comment about deleting items.
|
||||
if 0 < rq.tab.len:
|
||||
var
|
||||
key = rq.kFirst
|
||||
loopOK = true
|
||||
while loopOK:
|
||||
let item = rq.tab[key]
|
||||
loopOK = key != rq.kLast
|
||||
key = item.kNxt
|
||||
yield item.data
|
||||
|
||||
iterator nextPairs*[K,V](rq: var KeyedQueue[K,V]): KeyedQueuePair[K,V]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Iterate over all (key,value) pairs in the queue starting with the
|
||||
## `(rq.firstKey.value,rq.first.value.value)` key/item pair (if any). Using
|
||||
## the notation introduced with `rq.append` and `rq.prepend`, the iterator
|
||||
## processes *left* to *right*.
|
||||
##
|
||||
## See the note at the `nextKeys()` function comment about deleting items.
|
||||
if 0 < rq.tab.len:
|
||||
var
|
||||
key = rq.kFirst
|
||||
loopOK = true
|
||||
while loopOK:
|
||||
let
|
||||
yKey = key
|
||||
item = rq.tab[key]
|
||||
loopOK = key != rq.kLast
|
||||
key = item.kNxt
|
||||
yield KeyedQueuePair[K,V](key: yKey, data: item.data)
|
||||
|
||||
iterator prevKeys*[K,V](rq: var KeyedQueue[K,V]): K
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Reverse iterate over all keys in the queue starting with the
|
||||
## `rq.lastKey.value` key (if any). Using the notation introduced with
|
||||
## `rq.append` and `rq.prepend`, the iterator processes *right* to *left*.
|
||||
##
|
||||
## See the note at the `nextKeys()` function comment about deleting items.
|
||||
if 0 < rq.tab.len:
|
||||
var
|
||||
key = rq.kLast
|
||||
loopOK = true
|
||||
while loopOK:
|
||||
let yKey = key
|
||||
loopOK = key != rq.kFirst
|
||||
key = rq.tab[key].kPrv
|
||||
yield yKey
|
||||
|
||||
iterator prevValues*[K,V](rq: var KeyedQueue[K,V]): V
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Reverse iterate over all values in the queue starting with the
|
||||
## `rq.kLast.value.value` item value (if any). Using the notation introduced
|
||||
## with `rq.append` and `rq.prepend`, the iterator processes *right* to
|
||||
## *left*.
|
||||
##
|
||||
## See the note at the `nextKeys()` function comment about deleting items.
|
||||
if 0 < rq.tab.len:
|
||||
var
|
||||
key = rq.kLast
|
||||
loopOK = true
|
||||
while loopOK:
|
||||
let item = rq.tab[key]
|
||||
loopOK = key != rq.kFirst
|
||||
key = item.kPrv
|
||||
yield item.data
|
||||
|
||||
iterator prevPairs*[K,V](rq: var KeyedQueue[K,V]): KeyedQueuePair[K,V]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Reverse iterate over all (key,value) pairs in the queue starting with the
|
||||
## `(rq.lastKey.value,rq.last.value.value)` key/item pair (if any). Using
|
||||
## the notation introduced with `rq.append` and `rq.prepend`, the iterator
|
||||
## processes *right* to *left*.
|
||||
##
|
||||
## See the note at the `nextKeys()` function comment about deleting items.
|
||||
if 0 < rq.tab.len:
|
||||
var
|
||||
key = rq.kLast
|
||||
loopOK = true
|
||||
while loopOK:
|
||||
let
|
||||
yKey = key
|
||||
item = rq.tab[key]
|
||||
loopOK = key != rq.kFirst
|
||||
key = item.kPrv
|
||||
yield KeyedQueuePair[K,V](key: yKey, data: item.data)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,100 @@
|
|||
# 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.
|
||||
|
||||
## Keyed Queue, Debuugig support
|
||||
## =============================
|
||||
##
|
||||
|
||||
import
|
||||
std/tables,
|
||||
../keyed_queue,
|
||||
../results
|
||||
|
||||
type
|
||||
KeyedQueueInfo* = enum ##\
|
||||
## Error messages as returned by `verify()`
|
||||
kQOk = 0
|
||||
kQVfyFirstInconsistent
|
||||
kQVfyLastInconsistent
|
||||
kQVfyNoSuchTabItem
|
||||
kQVfyNoPrvTabItem
|
||||
kQVfyNxtPrvExpected
|
||||
kQVfyLastExpected
|
||||
kQVfyNoNxtTabItem
|
||||
kQVfyPrvNxtExpected
|
||||
kQVfyFirstExpected
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, debugging
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `$`*[K,V](item: KeyedQueueItem[K,V]): string =
|
||||
## Pretty print data container item.
|
||||
##
|
||||
## :CAVEAT:
|
||||
## This function needs working definitions for the `key` and `value` items:
|
||||
## ::
|
||||
## proc `$`*[K](key: K): string {.gcsafe,raises:[Defect,CatchableError].}
|
||||
## proc `$`*[V](value: V): string {.gcsafe,raises:[Defect,CatchableError].}
|
||||
##
|
||||
if item.isNil:
|
||||
"nil"
|
||||
else:
|
||||
"(" & $item.value & ", link[" & $item.prv & "," & $item.kNxt & "])"
|
||||
|
||||
proc verify*[K,V](rq: var KeyedQueue[K,V]): Result[void,(K,V,KeyedQueueInfo)]
|
||||
{.gcsafe,raises: [Defect,KeyError].} =
|
||||
## Check for consistency. Returns an error unless the argument
|
||||
## queue `rq` is consistent.
|
||||
let tabLen = rq.tab.len
|
||||
if tabLen == 0:
|
||||
return ok()
|
||||
|
||||
# Ckeck first and last items
|
||||
if rq.tab[rq.kFirst].kPrv != rq.tab[rq.kFirst].kNxt:
|
||||
return err((rq.kFirst, rq.tab[rq.kFirst].data, kQVfyFirstInconsistent))
|
||||
|
||||
if rq.tab[rq.kLast].kPrv != rq.tab[rq.kLast].kNxt:
|
||||
return err((rq.kLast, rq.tab[rq.kLast].data, kQVfyLastInconsistent))
|
||||
|
||||
# Just a return value
|
||||
var any: V
|
||||
|
||||
# Forward walk item list
|
||||
var key = rq.kFirst
|
||||
for _ in 1 .. tabLen:
|
||||
if not rq.tab.hasKey(key):
|
||||
return err((key, any, kQVfyNoSuchTabItem))
|
||||
if not rq.tab.hasKey(rq.tab[key].kNxt):
|
||||
return err((rq.tab[key].kNxt, rq.tab[key].data, kQVfyNoNxtTabItem))
|
||||
if key != rq.kLast and key != rq.tab[rq.tab[key].kNxt].kPrv:
|
||||
return err((key, rq.tab[rq.tab[key].kNxt].data, kQVfyNxtPrvExpected))
|
||||
key = rq.tab[key].kNxt
|
||||
if rq.tab[key].kNxt != rq.kLast:
|
||||
return err((key, rq.tab[key].data, kQVfyLastExpected))
|
||||
|
||||
# Backwards walk item list
|
||||
key = rq.kLast
|
||||
for _ in 1 .. tabLen:
|
||||
if not rq.tab.hasKey(key):
|
||||
return err((key, any, kQVfyNoSuchTabItem))
|
||||
if not rq.tab.hasKey(rq.tab[key].kPrv):
|
||||
return err((rq.tab[key].kPrv, rq.tab[key].data, kQVfyNoPrvTabItem))
|
||||
if key != rq.kFirst and key != rq.tab[rq.tab[key].kPrv].kNxt:
|
||||
return err((key, rq.tab[rq.tab[key].kPrv].data, kQVfyPrvNxtExpected))
|
||||
key = rq.tab[key].kPrv
|
||||
if rq.tab[key].kPrv != rq.kFirst:
|
||||
return err((key, rq.tab[key].data, kQVfyFirstExpected))
|
||||
|
||||
ok()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,316 @@
|
|||
# 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.
|
||||
|
||||
## Generic Sorted List Based on Red-black Trees
|
||||
## ============================================
|
||||
##
|
||||
## Due to the sort order fetch operations ge, le, etc., this API differs
|
||||
## considerably from the `table` API.
|
||||
##
|
||||
## Note that the list descriptor is a reference. So assigning an `sLstRef`
|
||||
## descriptor variable does *not* duplicate the descriptor but rather
|
||||
## add another link to the descriptor.
|
||||
##
|
||||
## Example:
|
||||
## ::
|
||||
## # create new list with integer keys, and integer values
|
||||
## var sl = SortedSet[int,int].init()
|
||||
##
|
||||
## # add some entries
|
||||
## for key in [208, 127, 106, 117, 49, 40, 171]:
|
||||
## let rc = sl.insert(key)
|
||||
## if rc.isOk:
|
||||
## # unique key, store some value
|
||||
## rc.value.data = -key
|
||||
##
|
||||
## # print entries with keys greater than 100 in natrual key order
|
||||
## block:
|
||||
## var rc = sl.ge(100)
|
||||
## while rc.isOk:
|
||||
## echo "*** item ", rc.value.key, " ", rc.value.data
|
||||
## w = sl.gt(w.value.key)
|
||||
##
|
||||
## # print all key/value entries in natrual key order
|
||||
## block:
|
||||
## var
|
||||
## walk = SortedSetWalkRef[K,V].init(sl)
|
||||
## rc = w.first
|
||||
## while rc.isOk:
|
||||
## echo "*** item ", rc.value.key, " ", rc.value.data
|
||||
## rc = w.next
|
||||
## # optional clean up, see comments on the destroy() directive
|
||||
## walk.destroy
|
||||
##
|
||||
import
|
||||
std/[tables],
|
||||
./sorted_set/[rbtree_delete,
|
||||
rbtree_desc,
|
||||
rbtree_find,
|
||||
rbtree_flush,
|
||||
rbtree_insert,
|
||||
rbtree_reset,
|
||||
rbtree_verify,
|
||||
rbtree_walk],
|
||||
./results
|
||||
|
||||
export
|
||||
RbInfo,
|
||||
RbResult,
|
||||
`isRed=`, # no need to export all of `rbtree_desc`
|
||||
results
|
||||
|
||||
type
|
||||
SortedSetItemRef*[K,V] = ref object ##\
|
||||
## Data value container as stored in the list/database
|
||||
key: K ## Sorter key, read-only
|
||||
data*: V ## Some data value, to be modified freely
|
||||
|
||||
SortedSet*[K,V] = object of RootObj ##\
|
||||
## Sorted list descriptor
|
||||
tree: RbTreeRef[SortedSetItemRef[K,V],K]
|
||||
|
||||
SortedSetWalkRef*[K,V] = ##\
|
||||
## Traversal/walk descriptor for sorted list
|
||||
RbWalkRef[SortedSetItemRef[K,V],K]
|
||||
|
||||
SortedSetResult*[K,V] = ##\
|
||||
## Data value container or error code, typically used as value \
|
||||
## returned from functions.
|
||||
RbResult[SortedSetItemRef[K,V]]
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc slstCmp[K,V](casket: SortedSetItemRef[K,V]; key: K): int =
|
||||
casket.key.cmp(key)
|
||||
|
||||
proc slstMkc[K,V](key: K): SortedSetItemRef[K,V] =
|
||||
SortedSetItemRef[K,V](key: key)
|
||||
|
||||
proc slstClup[K,V](c: var SortedSetItemRef[K,V]) =
|
||||
# ... some smart stuff here?
|
||||
c = nil # GC hint (if any, todo?)
|
||||
|
||||
|
||||
proc slstLt[K,V](a, b: SortedSetItemRef[K,V]): bool =
|
||||
## Debugging only
|
||||
a.slstCmp(b.key) < 0
|
||||
|
||||
proc slstPr(code: RbInfo; ctxInfo: string) =
|
||||
## Debugging only
|
||||
echo "*** sLst Error(", code, "): ", ctxInfo
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc init*[K,V](sl: var SortedSet[K,V]) =
|
||||
## Constructor for sorted list with key type `K` and data type `V`
|
||||
sl.tree = newRbTreeRef[SortedSetItemRef[K,V],K](
|
||||
cmp = proc(c: SortedSetItemRef[K,V]; k: K): int = c.slstCmp(k),
|
||||
mkc = proc(k: K): SortedSetItemRef[K,V] = slstMkc[K,V](k))
|
||||
|
||||
proc init*[K,V](T: type SortedSet[K,V]): T =
|
||||
## Variant of `init()`
|
||||
result.init
|
||||
|
||||
proc move*[K,V](sl: var SortedSet[K,V]): SortedSet[K,V] =
|
||||
## Return a shallow copy of the argument list `sl`, then reset `sl`.
|
||||
result.tree = sl.tree
|
||||
sl.init
|
||||
|
||||
proc reset*[K,V](sl: var SortedSet[K,V]) =
|
||||
## Reset list descriptor to its inital value. This function also de-registers
|
||||
## and flushes all traversal descriptors of type `SortedSetWalkRef`.
|
||||
sl.tree.rbTreeReset(clup = proc(c: var SortedSetItemRef[K,V]) = c.slstClup)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, getter, converter
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc key*[K,V](data: SortedSetItemRef[K,V]): K =
|
||||
## Getter, extracts the key from the data container item.
|
||||
data.key
|
||||
|
||||
proc len*[K,V](sl: var SortedSet[K,V]): int =
|
||||
## Number of list elements
|
||||
sl.tree.size
|
||||
|
||||
proc toSortedSetResult*[K,V](key: K; data: V): SortedSetResult[K,V] =
|
||||
## Helper, chreate `ok()` result
|
||||
ok(SortedSetItemRef[K,V](key: key, data: data))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, list operations
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc insert*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Insert `key`, returns data container item with the `key`. Function fails
|
||||
## if `key` exists in the list.
|
||||
sl.tree.rbTreeInsert(key)
|
||||
|
||||
proc findOrInsert*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Insert or find `key`, returns data container item with the `key`. This
|
||||
## function always succeeds (unless there is s problem with the list.)
|
||||
result = sl.tree.rbTreeInsert(key)
|
||||
if result.isErr:
|
||||
return sl.tree.rbTreeFindEq(key)
|
||||
|
||||
proc delete*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Delete `key` from list and return the data container item that was
|
||||
## holding the `key` if it was deleted.
|
||||
sl.tree.rbTreeDelete(key)
|
||||
|
||||
proc flush*[K,V](sl: var SortedSet[K,V]) =
|
||||
## Flush the sorted list, i.e. delete all entries. This function is
|
||||
## more efficient than running a `delete()` loop.
|
||||
sl.tree.rbTreeFlush(clup = proc(c: var SortedSetItemRef[K,V]) = c.slstClup)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, query functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc eq*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Find `key` in list, returns data container item with the `key` if it
|
||||
## exists.
|
||||
sl.tree.rbTreeFindEq(key)
|
||||
|
||||
proc le*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Find data container iten with *largest* key *less or equal* the argument
|
||||
## `key` in list and return it if found.
|
||||
sl.tree.rbTreeFindLe(key)
|
||||
|
||||
proc lt*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Find data container item with *largest* key *less than* the argument
|
||||
## `key` in list and return it if found.
|
||||
sl.tree.rbTreeFindLt(key)
|
||||
|
||||
proc ge*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Find data container item with *smallest* key *greater or equal* the
|
||||
## argument `key` in list and return it if found.
|
||||
sl.tree.rbTreeFindGe(key)
|
||||
|
||||
proc gt*[K,V](sl: var SortedSet[K,V]; key: K): SortedSetResult[K,V] =
|
||||
## Find data container item with *smallest* key *greater than* the argument
|
||||
## `key` in list and return it if found.
|
||||
sl.tree.rbTreeFindGt(key)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, walk/traversal functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc init*[K,V](T: type SortedSetWalkRef[K,V]; sl: var SortedSet[K,V]): T =
|
||||
## Open traversal descriptor on list and register it on the 'SortedSet`
|
||||
## descriptor.
|
||||
sl.tree.newRbWalk
|
||||
|
||||
proc destroy*[K,V](w: SortedSetWalkRef[K,V]) =
|
||||
## De-register and close the traversal descriptor. This function renders
|
||||
## the descriptor unusable, so it must be disposed of.
|
||||
##
|
||||
## This destructor function is crucial when insert/delete operations are
|
||||
## needed to run while traversals are open and not rewound. These
|
||||
## insert/delete operations modify the list so that `w.this`, `w.prev`,
|
||||
## etc. operations might fail. All traversal descriptors must then be
|
||||
## rewound or destroyed.
|
||||
w.rbWalkDestroy
|
||||
|
||||
proc first*[K,V](w: SortedSetWalkRef[K,V]): SortedSetResult[K,V] =
|
||||
## Rewind the traversal descriptor to the *least* list key and return
|
||||
## the corresponding data container item.
|
||||
##
|
||||
## When all open traversals are rewound, blockers due to insert/delete
|
||||
## list operations are reset.
|
||||
w.rbWalkFirst
|
||||
|
||||
proc last*[K,V](w: SortedSetWalkRef[K,V]): SortedSetResult[K,V] =
|
||||
## Rewind the traversal descriptor to the *greatest* list key and return
|
||||
## the corresponding data container item.
|
||||
##
|
||||
## When all open traversals are rewound, blockers due to insert/delete
|
||||
## list operations are reset.
|
||||
w.rbWalkLast
|
||||
|
||||
proc this*[K,V](w: SortedSetWalkRef[K,V]): SortedSetResult[K,V] =
|
||||
## Retrieve the *current* data container item. This is the same one retrieved
|
||||
## last with any of the traversal functions returning the data container item.
|
||||
##
|
||||
## Note that the current node becomes unavailable if it was recently deleted.
|
||||
w.rbWalkCurrent
|
||||
|
||||
proc next*[K,V](w: SortedSetWalkRef[K,V]): SortedSetResult[K,V] =
|
||||
## Move the traversal descriptor to the next *greater* key and return the
|
||||
## corresponding data container item. If this is the first call after
|
||||
## `newWalk()`, then `w.first` is called implicitly.
|
||||
##
|
||||
## If there were tree insert/delete operations, blockers might be active
|
||||
## causing this function to fail so that a rewind is needed.
|
||||
w.rbWalkNext
|
||||
|
||||
proc prev*[K,V](w: SortedSetWalkRef[K,V]): SortedSetResult[K,V] =
|
||||
## Move the traversal descriptor to the next *smaller* key and return the
|
||||
## corresponding data container item . If this is the first call after
|
||||
## `newWalk()`, then `w.last` is called implicitly.
|
||||
##
|
||||
## If there were tree insert/delete operations, blockers might be active
|
||||
## causing this function to fail so that a rewind is needed.
|
||||
w.rbWalkPrev
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public helpers, debugging
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `$`*[K,V](casket: SortedSetItemRef[K,V]): string =
|
||||
## Pretty printer
|
||||
##
|
||||
## :CAVEAT:
|
||||
## This function needs a working definition for the `data` item:
|
||||
## ::
|
||||
## proc `$`*[V](value: V): string {.gcsafe,raises:[Defect,CatchableError].}
|
||||
##
|
||||
if casket.isNil:
|
||||
return "nil"
|
||||
"(" & $casket.key & "," & $casket.data & ")"
|
||||
|
||||
proc `$`*[K,V](rc: SortedSetResult[K,V]): string =
|
||||
## Pretty printer
|
||||
##
|
||||
## :CAVEAT:
|
||||
## This function needs a working definition for the `data` item:
|
||||
## ::
|
||||
## proc `$`*[V](data: V): string {.gcsafe,raises:[Defect,CatchableError].}
|
||||
##
|
||||
if rc.isErr:
|
||||
return $rc.error
|
||||
$rc.value
|
||||
|
||||
proc verify*[K,V](sl: var SortedSet[K,V]):
|
||||
Result[void,(SortedSetItemRef[K,V],RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
## Checks for consistency, may print an error message. Returns `rbOk` if
|
||||
## the argument list `sl` is consistent. This function traverses all the
|
||||
## internal data nodes which might be time consuming. So it would not be
|
||||
## used in production code.
|
||||
##
|
||||
## :CAVEAT:
|
||||
## This function needs a working definition for the `data` item:
|
||||
## ::
|
||||
## proc `$`*[V](data: V): string {.gcsafe,raises:[Defect,CatchableError].}
|
||||
##
|
||||
sl.tree.rbTreeVerify(
|
||||
lt = proc(a, b: SortedSetItemRef[K,V]): bool = a.sLstLt(b),
|
||||
pr = proc(c: RbInfo; s: string) = c.slstPr(s))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,138 @@
|
|||
# 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
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,308 @@
|
|||
# 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.
|
||||
|
||||
## 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
|
||||
|
||||
import
|
||||
std/[tables],
|
||||
../results
|
||||
|
||||
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: [Defect].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions, constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc 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))
|
||||
|
||||
|
||||
proc 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 .. rbt.walkIdGen.high:
|
||||
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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc 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)
|
||||
|
||||
proc 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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc linkLeft*[C](node: RbNodeRef[C]): RbNodeRef[C] {.inline.} =
|
||||
## Getter, shortcut for `node.link[rbLeft]`
|
||||
node.link[rbLeft]
|
||||
|
||||
proc linkRight*[C](node: RbNodeRef[C]): RbNodeRef[C] {.inline.} =
|
||||
## Getter, shortcut for `node.link[rbRight]`
|
||||
node.link[rbRight]
|
||||
|
||||
proc isRed*[C](node: RbNodeRef[C]): bool {.inline.} =
|
||||
## Getter, `true` if node colour is read.
|
||||
not node.isNil and node.redColour
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `linkLeft=`*[C](node, child: RbNodeRef[C]) {.inline.} =
|
||||
## Getter, shortcut for `node.link[rbLeft] = child`
|
||||
node.link[rbLeft] = child
|
||||
|
||||
proc `linkRight=`*[C](node, child: RbNodeRef[C]) {.inline.} =
|
||||
## Getter, shortcut for `node.link[rbRight] = child`
|
||||
node.link[rbRight] = child
|
||||
|
||||
proc `isRed=`*[C](node: RbNodeRef[C]; value: bool) {.inline.} =
|
||||
## Setter, `true` sets red node colour.
|
||||
node.redColour = value
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public helpers: `rbDir` as array index
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `not`*(d: RbDir): RbDir {.inline.} =
|
||||
## Opposite direction of argument `d`.
|
||||
if d == rbLeft: rbRight else: rbLeft
|
||||
|
||||
proc toDir*(b: bool): RbDir {.inline.} =
|
||||
## Convert to link diection `rbLeft` (false) or `rbRight` (true).
|
||||
if b: rbRight else: rbLeft
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public pretty printer
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `$`*[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 & ")"
|
||||
|
||||
proc `$`*[C,K](rbt: RbTreeRef[C,K]): string =
|
||||
## Pretty printer
|
||||
if rbt.isNil:
|
||||
return "nil"
|
||||
"[" &
|
||||
"size=" & $rbt.size & "," &
|
||||
"gen=" & $rbt.walkIdGen & "," &
|
||||
"root=" & $rbt.root & "]"
|
||||
|
||||
proc `$`*[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
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,232 @@
|
|||
# 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,
|
||||
../results
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbTreeFindEq*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
|
||||
## Generic red-black tree function. Search for a data container `casket` of
|
||||
## type `C` in the red black tree which matches the argument `key`,
|
||||
## i.e. `rbt.cmp(casket,key) == 0`. If found, this data container `casket` is
|
||||
## returned, otherwise an error code is returned.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbfind()` 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)
|
||||
|
||||
if not rbt.cache.isNil and rbt.cmp(rbt.cache.casket,key) == 0:
|
||||
return ok(rbt.cache.casket)
|
||||
|
||||
var
|
||||
q = rbt.root
|
||||
while not q.isNil:
|
||||
let diff = rbt.cmp(q.casket,key)
|
||||
|
||||
if diff == 0:
|
||||
return ok(q.casket)
|
||||
|
||||
# FIXME: If the tree supports duplicates, they should be
|
||||
# chained to the right subtree for this to work
|
||||
let dir2 = (diff < 0).toDir
|
||||
q = q.link[dir2]
|
||||
|
||||
return err(rbNotFound)
|
||||
|
||||
|
||||
proc rbTreeFindGe*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
|
||||
## Generic red-black tree function. Search for the *smallest* data container
|
||||
## `casket` of type `C` which is *greater or equal* to the specified argument
|
||||
## `key` in the red black-tree. If such a data container is found in the
|
||||
## tree it is returned, otherwise an error code is returned.
|
||||
##
|
||||
## If found in the tree, this data container `casket` satisfies
|
||||
## `0 <= cmp(casket,key)` and there is no *smaller* data container relative
|
||||
## to `casket` in the red-black tree satisfying this condition.
|
||||
##
|
||||
##
|
||||
## For a more formal reasoning of *smaller* and *greater*, consider the
|
||||
## injection `mkc:K -> C` which is used to create data containers of type `C`
|
||||
## from a key of type `K`. Let `mkc':K -> C'` be the map onto the
|
||||
## equivalence class `C'` where `x` is in a class `mkc'(key)` if
|
||||
## `cmp(x,key) == 0` (i.e. `x` is a later modification of a data container
|
||||
## originally created as `mkc(key)`.)
|
||||
##
|
||||
## Then `mkc'` is an isomorphism.and there is the natural order relation on
|
||||
## `C'` which is extended from the order relation on `K`. So the returned
|
||||
## result is `min(v' of C': key <= v')` which is `mkc(key)` apart from
|
||||
## data container modifications.
|
||||
##
|
||||
if rbt.root.isNil:
|
||||
return err(rbEmptyTree)
|
||||
|
||||
# Always: not itemOk or key <= item
|
||||
var
|
||||
itemOk = false
|
||||
item: C
|
||||
q = rbt.root
|
||||
while true:
|
||||
var
|
||||
nxt: RbNodeRef[C]
|
||||
diff = rbt.cmp(q.casket,key)
|
||||
|
||||
if 0 < diff: # key < q.casket
|
||||
itemOk = true # remember item
|
||||
item = q.casket # now: key < item
|
||||
nxt = q.linkLeft # move left => get strictly smaller items
|
||||
|
||||
elif diff < 0:
|
||||
nxt = q.linkRight # move right => see strictly larger items
|
||||
|
||||
else:
|
||||
return ok(q.casket)
|
||||
|
||||
if nxt.isNil:
|
||||
if itemOk:
|
||||
return ok(item)
|
||||
break
|
||||
|
||||
q = nxt
|
||||
# End while
|
||||
|
||||
return err(rbNotFound)
|
||||
|
||||
|
||||
proc rbTreeFindGt*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
|
||||
## Generic red-black tree function. Search for the *smallest* data container
|
||||
## of type `C` which is *strictly greater* than the specified argument `key`
|
||||
## in the red-black tree.
|
||||
##
|
||||
## See comments on `rbTreeFindGe()` for a formal definition of how to apply
|
||||
## an order relation on `C`.
|
||||
if rbt.root.isNil:
|
||||
return err(rbEmptyTree)
|
||||
|
||||
# Always: not itemOk or key < item
|
||||
var
|
||||
itemOk = false
|
||||
item: C
|
||||
q = rbt.root
|
||||
while true:
|
||||
var
|
||||
nxt: RbNodeRef[C]
|
||||
diff = rbt.cmp(q.casket,key)
|
||||
|
||||
if 0 < diff: # key < q.casket
|
||||
itemOk = true # remember item
|
||||
item = q.casket # now: key < item
|
||||
nxt = q.linkLeft # move left => get strictly smaller items
|
||||
|
||||
else:
|
||||
nxt = q.linkRight # move right => see probably larger items
|
||||
|
||||
if nxt.isNil:
|
||||
if itemOk:
|
||||
return ok(item)
|
||||
break
|
||||
|
||||
q = nxt
|
||||
# End while
|
||||
|
||||
return err(rbNotFound)
|
||||
|
||||
|
||||
proc rbTreeFindLe*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
|
||||
## Generic red-black tree function. Search for the *greatest* data container
|
||||
## of type `C` which is *less than or equal* to the specified argument
|
||||
## `key` in the red-black tree.
|
||||
##
|
||||
## See comments on `rbTreeFindGe()` for a formal definition of how to apply
|
||||
## an order relation on `C`.
|
||||
if rbt.root.isNil:
|
||||
return err(rbEmptyTree)
|
||||
|
||||
# Always: not itemOk or item < key
|
||||
var
|
||||
itemOk = false
|
||||
item: C
|
||||
q = rbt.root
|
||||
while true:
|
||||
var
|
||||
nxt: RbNodeRef[C]
|
||||
diff = rbt.cmp(q.casket,key)
|
||||
|
||||
if diff < 0: # q.casket < key
|
||||
itemOk = true # remember item
|
||||
item = q.casket # now: item < key
|
||||
nxt = q.linkRight # move right => get strictly larger items
|
||||
|
||||
elif 0 < diff:
|
||||
nxt = q.linkLeft # move left => see strictly smaller items
|
||||
|
||||
else:
|
||||
return ok(q.casket)
|
||||
|
||||
if nxt.isNil:
|
||||
if itemOk:
|
||||
return ok(item)
|
||||
break
|
||||
|
||||
q = nxt
|
||||
# End while
|
||||
|
||||
return err(rbNotFound)
|
||||
|
||||
|
||||
proc rbTreeFindLt*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
|
||||
## Generic red-black tree function. Search for the *greatest* data container
|
||||
## of type `C` which is *strictly less* than the specified argument `key` in
|
||||
## the red-black tree.
|
||||
##
|
||||
## See comments on `rbTreeFindGe()` for a formal definition of how to apply
|
||||
## an order relation on `C`.
|
||||
if rbt.root.isNil:
|
||||
return err(rbEmptyTree)
|
||||
|
||||
# Always: not itemOk or item < key
|
||||
var
|
||||
itemOk = false
|
||||
item: C
|
||||
q = rbt.root
|
||||
while true:
|
||||
var
|
||||
nxt: RbNodeRef[C]
|
||||
diff = rbt.cmp(q.casket,key)
|
||||
|
||||
if diff < 0: # q.casket < key
|
||||
itemOk = true # remember item
|
||||
item = q.casket # now: item < key
|
||||
nxt = q.linkRight # move right => get larger items
|
||||
|
||||
else:
|
||||
nxt = q.linkLeft # move left => see probably smaller items
|
||||
|
||||
if nxt.isNil:
|
||||
if itemOk:
|
||||
return ok(item)
|
||||
break
|
||||
|
||||
q = nxt
|
||||
# End while
|
||||
|
||||
return err(rbNotFound)
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,95 @@
|
|||
# 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
|
||||
|
||||
type
|
||||
RbTreeFlushDel*[C] = proc(c: var C) {.gcsafe.}
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Private
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Default clean up function
|
||||
proc defaultClup[C](casket: var C) {.inline.} =
|
||||
# smarty here ...
|
||||
discard
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbTreeFlush*[C,K](rbt: RbTreeRef[C,K];
|
||||
clup: RbTreeFlushDel[C] = nil): bool
|
||||
{.gcsafe,discardable.} =
|
||||
## Clears/flushes a valid red-black tree while passing each data container
|
||||
## `casket` to the clean up argument function `clup()` before removing it.
|
||||
##
|
||||
## The flush function returns `false` and stops immediately if another flush
|
||||
## instance is already running. On a single thread execution model, this can
|
||||
## only happen if this very same function is called from within the cleanup
|
||||
## function argument `clup()`.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbdelete()` 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.dirty and rbTreeFlushDataFlag) != 0:
|
||||
return false
|
||||
rbt.dirty = rbt.dirty or rbTreeFlushDataFlag
|
||||
|
||||
# clear node cache
|
||||
rbt.cache = nil
|
||||
|
||||
var
|
||||
cleanUp = clup
|
||||
save: RbNodeRef[C]
|
||||
it = rbt.root
|
||||
|
||||
if cleanUp.isNil:
|
||||
# Need `proc()` wrapper around generic `defaultClup[C]()` as the function
|
||||
# pointer `defaultClup` exists only with its incarnation,
|
||||
cleanUp = proc(c: var C) = c.defaultClup
|
||||
|
||||
# GC hint, reset node reference early so there is no remaining
|
||||
# top level link left when cleaning up right node chain
|
||||
rbt.root = nil
|
||||
|
||||
# Rotate away the left links so that we can treat this like
|
||||
# the destruction of a linked list
|
||||
while not it.isNil:
|
||||
|
||||
if it.linkLeft.isNil:
|
||||
# No left links, just kill the node and move on
|
||||
save = it.linkRight
|
||||
|
||||
it.casket.cleanUp
|
||||
it = nil # GC hint
|
||||
|
||||
else:
|
||||
# Rotate away the left link and check again
|
||||
save = it.linkLeft
|
||||
it.linkLeft = save.linkRight
|
||||
save.linkRight = it
|
||||
|
||||
it = save
|
||||
# End while
|
||||
|
||||
rbt.size = 0
|
||||
rbt.dirty = rbTreeNodesDeletedFlag or rbTreeReBalancedFlag
|
||||
|
||||
true
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,131 @@
|
|||
# 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].}
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc insertRoot[C,K](rbt: RbTreeRef[C,K]; key: K): C {.inline.} =
|
||||
## Insert item `x` into an empty tree.
|
||||
rbt.root = RbNodeRef[C](
|
||||
casket: rbt.mkc(key))
|
||||
rbt.size = 1
|
||||
rbt.root.casket
|
||||
|
||||
proc insertNode[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] {.inline.} =
|
||||
## Insert item `key` into a non-empty tree.
|
||||
|
||||
doAssert not rbt.root.isNil
|
||||
|
||||
var
|
||||
dir = rbLeft
|
||||
last = dir # always previous value of `dir`
|
||||
insertOk = false
|
||||
head = RbNodeRef[C](
|
||||
link: [nil, rbt.root]) # black fake tree root
|
||||
|
||||
# ancestry line: t -> g -> p -> q
|
||||
greatGrandParent = head # ancestor, fake (black) super root
|
||||
grandParent: RbNodeRef[C] # grandparent => NIL
|
||||
parent: RbNodeRef[C] # parent => NIL
|
||||
q = rbt.root # initialise iterator to not-NIL tree root
|
||||
|
||||
# Search down the tree for a place to insert
|
||||
while true:
|
||||
|
||||
if q.isNil:
|
||||
# Insert new (red) node at the first NIL link
|
||||
insertOk = true
|
||||
q = RbNodeRef[C](
|
||||
casket: rbt.mkc(key))
|
||||
q.isRed = true
|
||||
parent.link[dir] = q
|
||||
|
||||
elif q.linkLeft.isRed and q.linkRight.isRed:
|
||||
# Simple red violation: colour flip
|
||||
q.isRed = true
|
||||
q.linkLeft.isRed = false # aka black
|
||||
q.linkRight.isRed = false # aka black
|
||||
|
||||
# Fix red violation: rotations necessary
|
||||
if q.isRed and parent.isRed:
|
||||
let dir2 = (greatGrandParent.linkRight == grandParent).toDir
|
||||
greatGrandParent.link[dir2] =
|
||||
if parent.link[last] == q: grandParent.rbTreeRotateSingle(not last)
|
||||
else: grandParent.rbTreeRotateDouble(not last)
|
||||
|
||||
# Mark traversal path unusable
|
||||
rbt.dirty = rbt.dirty or rbTreeReBalancedFlag
|
||||
|
||||
# Stop working if we inserted a node. This check also disallows
|
||||
# duplicates in the tree.
|
||||
let diff = rbt.cmp(q.casket,key)
|
||||
if diff == 0:
|
||||
break ;
|
||||
|
||||
last = dir
|
||||
dir = (diff < 0).toDir
|
||||
|
||||
# Shift the helpers down the ancestry line
|
||||
if not grandParent.isNil:
|
||||
greatGrandParent = grandParent
|
||||
grandParent = parent
|
||||
parent = q
|
||||
q = q.link[dir]
|
||||
|
||||
# End while
|
||||
|
||||
# Update the root (it may be different)
|
||||
rbt.root = head.linkRight
|
||||
|
||||
# Make the root black for simplified logic
|
||||
rbt.root.isRed = false # aka black
|
||||
|
||||
# Save last node in cache (speeds up some find operation)
|
||||
rbt.cache = q
|
||||
|
||||
if insertOk:
|
||||
rbt.size.inc
|
||||
return ok(q.casket)
|
||||
|
||||
return err(rbExists)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbTreeInsert*[C,K](rbt: RbTreeRef[C,K]; key: K): RbResult[C] =
|
||||
## Generic red-black tree function, inserts a data container `casket` derived
|
||||
## from argument `key` into the red-black tree.
|
||||
##
|
||||
## If a new node was successfully created, the function returns the `casket`
|
||||
## data container matching the `key` argument (i.e.
|
||||
## `rbt.cmp(casket,key) == 0`). Otherwise, if the `key` argument was in the
|
||||
## tree already an error code is returned. In that case, the data container
|
||||
## can can be retrieved with `rbTreeFindEq()`.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbinsert()` 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 ok(rbt.insertRoot(key))
|
||||
rbt.insertNode(key)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,36 @@
|
|||
# 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_flush,
|
||||
./rbtree_walk
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbTreeReset*[C,K](rbt: RbTreeRef[C,K]; clup: RbTreeFlushDel[C] = nil) =
|
||||
## Destroys/clears the argumnnt red-black tree descriptor and all registered
|
||||
## walk descriptors by calling `rbTreeFlush()` and `rbWalkDestroy()`.
|
||||
##
|
||||
## The function argument `clup` is passed on to `rbTreeFlush()`.
|
||||
##
|
||||
## After return, the argument tree descriptor is reset to its initial and
|
||||
## empty state.
|
||||
rbt.rbWalkDestroyAll
|
||||
rbt.rbTreeFlush(clup = clup)
|
||||
rbt.dirty = 0
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,52 @@
|
|||
# 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
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
proc rbTreeRotateSingle*[C](node: RbNodeRef[C]; dir: RbDir): RbNodeRef[C] =
|
||||
## Perform a single red-black tree rotation in the specified direction.
|
||||
## This function assumes that all nodes are valid for a rotation.
|
||||
##
|
||||
## Params:
|
||||
## :node: The root node to rotate around
|
||||
## :dir: The direction to rotate (left or right)
|
||||
##
|
||||
## Returns:
|
||||
## The new root after rotation
|
||||
##
|
||||
let save = node.link[not dir]
|
||||
|
||||
node.link[not dir] = save.link[dir]
|
||||
save.link[dir] = node
|
||||
|
||||
node.isRed = true
|
||||
save.isRed = false # aka black
|
||||
|
||||
save
|
||||
|
||||
|
||||
proc rbTreeRotateDouble*[C](node: RbNodeRef[C]; dir: RbDir): RbNodeRef[C] =
|
||||
## Perform a double red-black rotation in the specified direction.
|
||||
## This function assumes that all nodes are valid for a rotation.
|
||||
##
|
||||
## Params:
|
||||
## :node: The root node to rotate around
|
||||
## :dir: The direction to rotate (0 = left, 1 = right)
|
||||
##
|
||||
## Returns:
|
||||
## The new root after rotation.
|
||||
##
|
||||
node.link[not dir] = node.link[not dir].rbTreeRotateSingle(not dir)
|
||||
node.rbTreeRotateSingle(dir)
|
||||
|
||||
# End
|
|
@ -0,0 +1,195 @@
|
|||
# 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,
|
||||
../results
|
||||
|
||||
type
|
||||
RbLtFn*[C] = ##\
|
||||
## Compare two data containers (rather than a container against a key)
|
||||
## for the equivalent of `a < b`
|
||||
proc(a, b: C): bool {.gcsafe.}
|
||||
|
||||
RbPrnFn* = ##\
|
||||
## Handle error message
|
||||
proc(code: RbInfo; ctxInfo: string)
|
||||
{.gcsafe, raises: [Defect,CatchableError].}
|
||||
|
||||
RbDdebug[C,K] = object
|
||||
tree: RbTreeRef[C,K] ## tree, not-Nil
|
||||
node: RbNodeRef[C] ## current node
|
||||
level: int ## red + black recursion level
|
||||
blkLevel: int ## black recursion level
|
||||
blkDepth: int ## expected black node chain length (unless zero)
|
||||
lt: RbLtFn[C] ## vfy less than
|
||||
pr: RbPrnFn
|
||||
msg: string ## collect data
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Private
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc pp[C](n: RbNodeRef[C]): string =
|
||||
if n.isNil:
|
||||
return "nil"
|
||||
result = $n.casket
|
||||
if n.isRed:
|
||||
result &= "~red"
|
||||
else:
|
||||
result &= "~black"
|
||||
|
||||
proc doError[C,K](d: var RbDdebug[C,K]; t: RbInfo; s: string):
|
||||
Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
if not d.pr.isNil:
|
||||
var msg = s &
|
||||
": <" & d.node.pp &
|
||||
" link[" & d.node.linkLeft.pp &
|
||||
", " & d.node.linkRight.pp & "]>"
|
||||
d.pr(t, msg)
|
||||
err((d.node.casket,t))
|
||||
|
||||
proc rootIsRed[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyRootIsRed, "Root node is red")
|
||||
|
||||
|
||||
proc redNodeRedLinkLeft[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyRedParentRedLeftLink, "Parent node and left link red")
|
||||
|
||||
proc redNodeRedLinkRight[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyRedParentRedRightLink, "Parent node and right link red")
|
||||
|
||||
proc redNodeRedLinkBoth[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyRedParentRedBothLinks, "Parent node and both links red")
|
||||
|
||||
|
||||
proc linkLeftCompError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyLeftLinkGtParent, "Left node greater than parent")
|
||||
|
||||
proc linkRightCompError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyRightLinkLtParent, "Right node greater than parent")
|
||||
|
||||
proc linkBothCompError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyBothLinkCmpParentReversed,
|
||||
"Left node greater than parent greater than right node")
|
||||
|
||||
proc blackChainLevelError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
d.doError(rbVfyBlackChainLevelMismatch,
|
||||
"Inconsistent length of black node chains")
|
||||
|
||||
|
||||
proc subTreeVerify[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
let node = d.node
|
||||
doAssert not node.isNil
|
||||
|
||||
# Check for double red link violation
|
||||
if node.isRed:
|
||||
if node.linkLeft.isRed:
|
||||
if node.linkRight.isRed:
|
||||
return d.redNodeRedLinkBoth
|
||||
return d.redNodeRedLinkLeft
|
||||
if node.linkRight.isRed:
|
||||
return d.redNodeRedLinkRight
|
||||
|
||||
# ok node is black, check the values if `lt` is available
|
||||
if not d.lt.isNil:
|
||||
|
||||
let
|
||||
linkLeft = node.linkLeft
|
||||
leftOk = linkLeft.isNil or d.lt(linkLeft.casket,node.casket)
|
||||
|
||||
linkRight = node.linkRight
|
||||
rightOk = linkRight.isNil or d.lt(node.casket,linkRight.casket)
|
||||
|
||||
if not leftOk:
|
||||
if not rightOk:
|
||||
return d.linkBothCompError
|
||||
return d.linkLeftCompError
|
||||
|
||||
if not rightOk:
|
||||
return d.linkRightCompError
|
||||
|
||||
# update nesting level and black chain length
|
||||
d.level.inc
|
||||
if not node.isRed:
|
||||
d.blkLevel.inc
|
||||
|
||||
if node.linkLeft.isNil and node.linkRight.isNil:
|
||||
# verify black chain length
|
||||
if d.blkDepth == 0:
|
||||
d.blkDepth = d.blkLevel
|
||||
elif d.blkDepth != d.blkLevel:
|
||||
return d.blackChainLevelError
|
||||
|
||||
if not node.linkLeft.isNil:
|
||||
d.node = node.linkLeft
|
||||
let rc = d.subTreeVerify
|
||||
if rc.isErr:
|
||||
return rc ;
|
||||
|
||||
if not node.linkRight.isNil:
|
||||
d.node = node.linkRight
|
||||
let rc = d.subTreeVerify
|
||||
if rc.isErr:
|
||||
return rc ;
|
||||
|
||||
d.level.dec
|
||||
if not node.isRed:
|
||||
d.blkLevel.dec
|
||||
|
||||
ok()
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbTreeVerify*[C,K](rbt: RbTreeRef[C,K];
|
||||
lt: RbLtFn[C] = nil; pr: RbPrnFn = nil):
|
||||
Result[void,(C,RbInfo)]
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
## Verifies the argument tree `rbt` for
|
||||
## * No consecutively linked red nodes down the tree
|
||||
## * Link consisteny: value(leftLink) < value(node) < value(rightLink). This
|
||||
## check needs to have the argument `lt` defined, otherwise this check is
|
||||
## skipped
|
||||
## * Black length: verify that all node chains down the tree count the same
|
||||
## lengths
|
||||
##
|
||||
## The function returns `rbOk` unless an error is found. If `pr` is defined,
|
||||
## this function is called with some error code and context information.
|
||||
if rbt.root.isNil:
|
||||
return ok()
|
||||
|
||||
var d = RbDdebug[C,K](
|
||||
tree: rbt,
|
||||
node: rbt.root,
|
||||
lt: lt,
|
||||
pr: pr)
|
||||
|
||||
if rbt.root.isRed:
|
||||
return d.rootIsRed
|
||||
|
||||
d.subTreeVerify
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,288 @@
|
|||
# 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
|
||||
std/[tables],
|
||||
./rbtree_desc,
|
||||
../results
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Priv
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc walkMove[C,K](w: RbWalkRef[C,K]; dir: RbDir): RbResult[C] =
|
||||
## Traverse a red black tree in the user-specified direction.
|
||||
##
|
||||
## :Returns:
|
||||
## Next data value in the specified direction
|
||||
##
|
||||
## Ackn:
|
||||
## `move()` from jsw_rbtree.c
|
||||
##
|
||||
w.start = false
|
||||
|
||||
if not w.node.link[dir].isNil:
|
||||
|
||||
# Continue down this branch
|
||||
w.path[w.top] = w.node
|
||||
w.top.inc
|
||||
w.node = w.node.link[dir]
|
||||
|
||||
while not w.node.link[not dir].isNil:
|
||||
w.path[w.top] = w.node
|
||||
w.top.inc
|
||||
|
||||
if w.path.len <= w.top:
|
||||
w.path.setLen(w.path.len + 10)
|
||||
w.node = w.node.link[not dir]
|
||||
|
||||
return ok(w.node.casket)
|
||||
|
||||
# Move to the next branch
|
||||
while 0 < w.top:
|
||||
let last = w.node
|
||||
w.top.dec
|
||||
w.node = w.path[w.top]
|
||||
|
||||
if last != w.node.link[dir]:
|
||||
return ok(w.node.casket)
|
||||
|
||||
# now: w.top == 0
|
||||
w.node = nil
|
||||
w.stop = true
|
||||
return err(rbEndOfWalk)
|
||||
|
||||
|
||||
proc walkStart[C,K](w: RbWalkRef[C,K]; dir: RbDir): RbResult[C] =
|
||||
## Rewind the traversal position to the left-most or-right-most position
|
||||
## as defined by the function argument `dir`. After successfully rewinding,
|
||||
## the desriptor is in `start` position (see `walkClearDirty()`).
|
||||
##
|
||||
## :Returns:
|
||||
## Left-most or-right-most data container.
|
||||
##
|
||||
## :Ackn:
|
||||
## see start() from jsw_rbtree.c
|
||||
##
|
||||
w.node = w.tree.root
|
||||
w.top = 0
|
||||
|
||||
if w.node.isNil:
|
||||
return err(rbWalkClosed)
|
||||
|
||||
# Save the path for later traversal
|
||||
while not w.node.link[dir].isNil:
|
||||
|
||||
if w.path.len <= w.top:
|
||||
w.path.setLen(w.path.len + 10)
|
||||
|
||||
w.path[w.top] = w.node
|
||||
w.top.inc
|
||||
|
||||
w.node = w.node.link[dir]
|
||||
|
||||
w.start = true
|
||||
return ok(w.node.casket)
|
||||
|
||||
|
||||
proc walkClearDirtyFlags[C,K](w: RbWalkRef[C,K]): bool =
|
||||
## Clear dirty flag if all traversal descriptors are in `start` postion.
|
||||
if w.tree.dirty != 0:
|
||||
for u in w.tree.walks.values:
|
||||
# At least one is not rewound => fail
|
||||
if not u.start:
|
||||
return false
|
||||
w.tree.dirty = 0
|
||||
|
||||
w.stop = false
|
||||
return true
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public constructor/desctructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc newRbWalk*[C,K](rbt: RbTreeRef[C,K]): RbWalkRef[C,K] =
|
||||
## Generic red-black tree function, creates a new traversal descriptor on the
|
||||
## argument red-black tree `rbt`.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbtnew()` from jsw_rbtree.c from captured C library
|
||||
## `jsw_rbtree.zip <https://web.archive.org/web/20160428112900/http://eternallyconfuzzled.com/libs/jsw_rbtree.zip>`_.
|
||||
##
|
||||
result = RbWalkRef[C,K](
|
||||
id: rbt.newWalkId,
|
||||
tree: rbt,
|
||||
path: newSeq[RbNodeRef[C]](32), # space for approx 2^16 leafs
|
||||
start: true) # unblock for `walkClearDirty()`
|
||||
doAssert result.id != 0
|
||||
rbt.walks[result.id] = result # register in parent descriptor
|
||||
|
||||
|
||||
proc rbWalkDestroy*[C,K](w: RbWalkRef[C,K]) =
|
||||
## Explicit destructor for current walk descriptor `w`. Clears the descriptor
|
||||
## argument `w`.
|
||||
##
|
||||
## This destructor function is crucial when insert/delete tree operations
|
||||
## are executed while traversals are open. These insert/delete functions
|
||||
## modify the tree so that `rbWalkThis()`, `rbWalkPrev()`, etc. operations
|
||||
## will fail. All traversal descriptors must then be rewound or destroyed.
|
||||
if not w.tree.isNil:
|
||||
w.tree.walks.del(w.id)
|
||||
w.tree = nil # notify GC
|
||||
w.node = nil
|
||||
w.path = @[]
|
||||
w[].reset
|
||||
|
||||
proc rbWalkDestroyAll*[C,K](rbt: RbTreeRef[C,K]) =
|
||||
## Apply `rbWalkDestroy()` to all registered walk descriptors.
|
||||
for w in rbt.walks.values:
|
||||
w.tree = nil # notify GC (if any, todo?)
|
||||
w.node = nil
|
||||
w.path = @[]
|
||||
w[].reset # clear
|
||||
rbt.walks = initTable[uint,RbWalkRef[C,K]](1)
|
||||
|
||||
proc rbWalkDestroyAll*[C,K](w: RbWalkRef[C,K]) =
|
||||
## Variant of `rbWalkDestroyAll()`
|
||||
if not w.tree.isNil:
|
||||
w.tree.rbWalkDestroyAll
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public functions: rewind
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbWalkFirst*[C,K](w: RbWalkRef[C,K]): RbResult[C] =
|
||||
## Move to the beginning of the tree (*smallest* item) and return the
|
||||
## corresponding data container of type `C`.
|
||||
##
|
||||
## When all traversals are rewound, blockers due to tree insert/delete
|
||||
## operations are reset.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbtfirst()` 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 w.tree.isNil:
|
||||
return err(rbWalkClosed)
|
||||
|
||||
# Reset dirty flag unless other traversal descriptors are open
|
||||
if not w.walkClearDirtyFlags:
|
||||
return err(rbWalkBlocked)
|
||||
|
||||
return w.walkStart(rbLeft)
|
||||
|
||||
|
||||
proc rbWalkLast*[C,K](w: RbWalkRef[C,K]): RbResult[C] =
|
||||
## Move to the end of the tree (*greatest* item) and return the corresponding
|
||||
## data container of type `C`.
|
||||
##
|
||||
## When all traversals are rewound, blockers due to tree insert/delete
|
||||
## operations are reset.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbtlast()` 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 w.tree.isNil:
|
||||
return err(rbWalkClosed)
|
||||
|
||||
# Reset dirty flag unless other traversal descriptors are open
|
||||
if not w.walkClearDirtyFlags:
|
||||
return err(rbWalkBlocked)
|
||||
|
||||
return w.walkStart(rbRight)
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# Public functions: traversal, get data entry
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc rbWalkCurrent*[C,K](w: RbWalkRef[C,K]): RbResult[C] =
|
||||
## Retrieves the data container of type `C` for the current node. Note that
|
||||
## the current node becomes unavailable if it was recently deleted.
|
||||
##
|
||||
if w.node.isNil:
|
||||
if w.tree.isNil:
|
||||
return err(rbWalkClosed)
|
||||
return err(rbEndOfWalk)
|
||||
|
||||
# Node exits => tree exists
|
||||
if (w.tree.dirty and rbTreeNodesDeletedFlag) != 0:
|
||||
return err(rbWalkBlocked)
|
||||
|
||||
return ok(w.node.casket)
|
||||
|
||||
|
||||
|
||||
proc rbWalkNext*[C,K](w: RbWalkRef[C,K]): RbResult[C] =
|
||||
## Traverse to the next value in ascending order and return the corresponding
|
||||
## data container of type `C`. If this is the first call after `newRbWalk()`,
|
||||
## then `rbWalkFirst()` is called implicitly.
|
||||
##
|
||||
## If there were tree insert/delete operations, blockers might be active
|
||||
## causing this function to fail so that a rewind is needed.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbtnext()` 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 not w.node.isNil:
|
||||
# Node exits => tree exists
|
||||
if w.tree.dirty != 0:
|
||||
return err(rbWalkBlocked)
|
||||
return w.walkMove(rbRight)
|
||||
|
||||
# End of travesal reached?
|
||||
if w.stop:
|
||||
return err(rbEndOfWalk)
|
||||
if w.tree.isNil:
|
||||
return err(rbWalkClosed)
|
||||
|
||||
# Reset dirty flag unless other traversal descriptors are open
|
||||
if not w.walkClearDirtyFlags:
|
||||
return err(rbWalkBlocked)
|
||||
|
||||
return w.walkStart(rbLeft) # minimum index item
|
||||
|
||||
|
||||
proc rbWalkPrev*[C,K](w: RbWalkRef[C,K]): RbResult[C] =
|
||||
## Traverse to the next value in descending order and return the
|
||||
## corresponding data container of type `C`. If this is the first call
|
||||
## after `newRbWalk()`, then `rbWalkLast()` is called implicitly.
|
||||
##
|
||||
## If there were tree insert/delete operations, blockers might be active
|
||||
## causing this function to fail so that a rewind is needed.
|
||||
##
|
||||
## :Ackn:
|
||||
## `jsw_rbtprev()` 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 not w.node.isNil:
|
||||
# Node exits => tree exists
|
||||
if w.tree.dirty != 0:
|
||||
return err(rbWalkBlocked)
|
||||
return w.walkMove(rbLeft)
|
||||
|
||||
# End of travesal reached?
|
||||
if w.stop:
|
||||
return err(rbEndOfWalk)
|
||||
if w.tree.isNil:
|
||||
return err(rbWalkClosed)
|
||||
|
||||
# Reset dirty flag unless other traversal descriptors are open
|
||||
if not w.walkClearDirtyFlags:
|
||||
return err(rbWalkBlocked)
|
||||
|
||||
return w.walkStart(rbRight) # maximum index item
|
||||
|
||||
# ----------------------------------------------------------------------- ------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -21,10 +21,12 @@ import
|
|||
test_ctops,
|
||||
test_endians2,
|
||||
test_io2,
|
||||
test_keyed_queue,
|
||||
test_macros,
|
||||
test_objects,
|
||||
test_ptrops,
|
||||
test_sequtils2,
|
||||
test_sorted_set,
|
||||
test_results,
|
||||
test_varints,
|
||||
test_winacl
|
||||
|
|
|
@ -0,0 +1,543 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2019 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
|
||||
std/[algorithm, sequtils, strformat, strutils, tables],
|
||||
../stew/keyed_queue,
|
||||
../stew/keyed_queue/kq_debug,
|
||||
unittest
|
||||
|
||||
const
|
||||
usedStrutils = newSeq[string]().join(" ")
|
||||
|
||||
lruCacheLimit = 10
|
||||
lruCacheModulo = 13
|
||||
|
||||
keyList = [
|
||||
185, 208, 53, 54, 196, 189, 187, 117, 94, 29, 6, 173, 207, 45, 31,
|
||||
208, 127, 106, 117, 49, 40, 171, 6, 94, 84, 60, 125, 87, 168, 183,
|
||||
200, 155, 34, 27, 67, 107, 108, 223, 249, 4, 113, 9, 205, 100, 77,
|
||||
224, 19, 196, 14, 83, 145, 154, 95, 56, 236, 97, 115, 140, 134, 97,
|
||||
153, 167, 23, 17, 182, 116, 253, 32, 108, 148, 135, 169, 178, 124, 147,
|
||||
231, 236, 174, 211, 247, 22, 118, 144, 224, 68, 124, 200, 92, 63, 183,
|
||||
56, 107, 45, 180, 113, 233, 59, 246, 29, 212, 172, 161, 183, 207, 189,
|
||||
56, 198, 130, 62, 28, 53, 122]
|
||||
|
||||
type
|
||||
KUQueue = # mind the kqueue module from the nim standard lib
|
||||
KeyedQueue[uint,uint]
|
||||
|
||||
LruCache = object
|
||||
size: int
|
||||
q: KUQueue
|
||||
|
||||
let
|
||||
noisy = defined(debug)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Debugging
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `$`(rc: KeyedQueuePair[uint,uint]): string =
|
||||
"(" & $rc.key & "," & $rc.data & ")"
|
||||
|
||||
proc `$`(rc: Result[KeyedQueuePair[uint,uint],void]): string =
|
||||
result = "<"
|
||||
if rc.isOK:
|
||||
result &= $rc.value.key & "," & $rc.value.data
|
||||
result &= ">"
|
||||
|
||||
proc `$`(rc: Result[uint,void]): string =
|
||||
result = "<"
|
||||
if rc.isOK:
|
||||
result &= $rc.value
|
||||
result &= ">"
|
||||
|
||||
proc say(noisy = false; pfx = "***"; args: varargs[string, `$`]) =
|
||||
if noisy:
|
||||
if args.len == 0:
|
||||
echo "*** ", pfx
|
||||
elif 0 < pfx.len and pfx[^1] != ' ':
|
||||
echo pfx, " ", args.toSeq.join
|
||||
else:
|
||||
echo pfx, args.toSeq.join
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Converters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc toValue(n: int): uint =
|
||||
(n + 1000).uint
|
||||
|
||||
proc fromValue(n: uint): int =
|
||||
(n - 1000).int
|
||||
|
||||
proc toKey(n: int): uint =
|
||||
n.uint
|
||||
|
||||
proc fromKey(n: uint): int =
|
||||
n.int
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc lruValue(lru: var LruCache; n: int): uint =
|
||||
let
|
||||
key = n.toKey
|
||||
rc = lru.q.lruFetch(key)
|
||||
if rc.isOK:
|
||||
return rc.value
|
||||
lru.q.lruAppend(key, key.fromKey.toValue, lru.size)
|
||||
|
||||
proc toLruCache(a: openArray[int]): LruCache =
|
||||
result.size = lruCacheLimit
|
||||
for n in a.toSeq.mapIt(it mod lruCacheModulo):
|
||||
doAssert result.lruValue(n) == n.toValue
|
||||
|
||||
proc toQueue(a: openArray[int]): KUQueue =
|
||||
for n in a:
|
||||
result[n.toKey] = n.toValue
|
||||
|
||||
proc toUnique(a: openArray[int]): seq[uint] =
|
||||
var q = a.toQueue
|
||||
toSeq(q.nextKeys)
|
||||
|
||||
proc addOrFlushGroupwise(rq: var KUQueue;
|
||||
grpLen: int; seen: var seq[int]; n: int;
|
||||
noisy = true) =
|
||||
seen.add n
|
||||
if seen.len < grpLen:
|
||||
return
|
||||
|
||||
# flush group-wise
|
||||
let rqLen = rq.len
|
||||
noisy.say "updateSeen: deleting ", seen.mapIt($it).join(" ")
|
||||
for a in seen:
|
||||
doAssert rq.delete(a.toKey).value.data == a.toValue
|
||||
doAssert rqLen == seen.len + rq.len
|
||||
seen.setLen(0)
|
||||
|
||||
proc compileGenericFunctions(rq: var KUQueue) =
|
||||
## Verifies that functions compile, at all
|
||||
rq.del(0)
|
||||
rq[0] = 0 # so `rq[0]` works
|
||||
discard rq[0]
|
||||
|
||||
let ignoreValues = (
|
||||
(rq.append(0,0), rq.push(0,0),
|
||||
rq.replace(0,0),
|
||||
rq.prepend(0,0), rq.unshift(0,0),
|
||||
rq.shift, rq.shiftKey, rq.shiftValue,
|
||||
rq.pop, rq.popKey, rq.popValue,
|
||||
rq.delete(0)),
|
||||
|
||||
(rq.hasKey(0), rq.eq(0)),
|
||||
|
||||
(rq.firstKey, rq.secondKey, rq.beforeLastKey, rq.lastKey,
|
||||
rq.nextKey(0), rq.prevKey(0)),
|
||||
|
||||
(rq.first, rq.second, rq.beforeLast, rq.last,
|
||||
rq.next(0), rq.prev(0)),
|
||||
|
||||
(rq.firstValue, rq.secondValue, rq.beforeLastValue, rq.lastValue),
|
||||
|
||||
(rq == rq, rq.len),
|
||||
|
||||
(toSeq(rq.nextKeys), toSeq(rq.nextValues), toSeq(rq.nextPairs),
|
||||
toSeq(rq.prevKeys), toSeq(rq.prevValues), toSeq(rq.prevPairs)))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Test Runners
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
let
|
||||
uniqueKeys = keyList.toUnique
|
||||
numUniqeKeys = keyList.toSeq.mapIt((it,false)).toTable.len
|
||||
numKeyDups = keyList.len - numUniqeKeys
|
||||
|
||||
suite "KeyedQueue: Data queue with keyed random access":
|
||||
|
||||
block:
|
||||
var
|
||||
fwdRq, revRq: KUQueue
|
||||
fwdRej, revRej: seq[int]
|
||||
|
||||
test &"All functions smoke test":
|
||||
var rq: KeyedQueue[uint,uint]
|
||||
rq.compileGenericFunctions
|
||||
|
||||
test &"Append/traverse {keyList.len} items, " &
|
||||
&"rejecting {numKeyDups} duplicates":
|
||||
var
|
||||
rq: KUQueue
|
||||
rej: seq[int]
|
||||
for n in keyList:
|
||||
if not rq.push(n.toKey, n.toValue): # synonymous for append()
|
||||
rej.add n
|
||||
let check = rq.verify
|
||||
if check.isErr:
|
||||
check check.error[2] == kQOk
|
||||
check rq.len == numUniqeKeys
|
||||
check rej.len == numKeyDups
|
||||
check rq.len + rej.len == keyList.len
|
||||
fwdRq = rq
|
||||
fwdRej = rej
|
||||
|
||||
check uniqueKeys == toSeq(rq.nextKeys)
|
||||
check uniqueKeys == toSeq(rq.prevKeys).reversed
|
||||
check uniqueKeys.len == numUniqeKeys
|
||||
|
||||
test &"Prepend/traverse {keyList.len} items, " &
|
||||
&"rejecting {numKeyDups} duplicates":
|
||||
var
|
||||
rq: KUQueue
|
||||
rej: seq[int]
|
||||
for n in keyList:
|
||||
if not rq.unshift(n.toKey, n.toValue): # synonymous for prepend()
|
||||
rej.add n
|
||||
let check = rq.verify
|
||||
if check.isErr:
|
||||
check check.error[2] == kQOk
|
||||
check rq.len == numUniqeKeys
|
||||
check rej.len == numKeyDups
|
||||
check rq.len + rej.len == keyList.len
|
||||
check toSeq(rq.nextKeys) == toSeq(rq.prevKeys).reversed
|
||||
revRq = rq
|
||||
revRej = rej
|
||||
|
||||
test "Compare previous forward/reverse queues":
|
||||
check 0 < fwdRq.len
|
||||
check 0 < revRq.len
|
||||
check toSeq(fwdRq.nextKeys) == toSeq(revRq.prevKeys)
|
||||
check toSeq(fwdRq.prevKeys) == toSeq(revRq.nextKeys)
|
||||
check fwdRej.sorted == revRej.sorted
|
||||
|
||||
test "Delete corresponding entries by keyed access from previous queues":
|
||||
var seen: seq[int]
|
||||
let sub7 = keyList.len div 7
|
||||
for n in toSeq(countUp(0,sub7)).concat(toSeq(countUp(3*sub7,4*sub7))):
|
||||
let
|
||||
key = keyList[n].toKey
|
||||
canDeleteOk = (key.fromKey notin seen)
|
||||
|
||||
eqFwdData = fwdRq.eq(key)
|
||||
eqRevData = revRq.eq(key)
|
||||
|
||||
if not canDeleteOk:
|
||||
check eqFwdData.isErr
|
||||
check eqRevData.isErr
|
||||
else:
|
||||
check eqFwdData.isOk
|
||||
check eqRevData.isOk
|
||||
let
|
||||
eqFwdEq = fwdRq.eq(eqFwdData.value.fromValue.toKey)
|
||||
eqRevEq = revRq.eq(eqRevData.value.fromValue.toKey)
|
||||
check eqFwdEq.isOk
|
||||
check eqRevEq.isOk
|
||||
let
|
||||
eqFwdKey = eqFwdEq.value.fromValue.toKey
|
||||
eqRevKey = eqRevEq.value.fromValue.toKey
|
||||
check eqFwdKey == key
|
||||
check eqRevKey == key
|
||||
|
||||
let
|
||||
fwdData = fwdRq.delete(key)
|
||||
fwdRqCheck = fwdRq.verify
|
||||
revData = revRq.delete(key)
|
||||
revRqCheck = revRq.verify
|
||||
|
||||
if key.fromKey notin seen:
|
||||
seen.add key.fromKey
|
||||
|
||||
if fwdRqCheck.isErr:
|
||||
check fwdRqCheck.error[2] == kQOk
|
||||
check fwdData.isOk == canDeleteOk
|
||||
if revRqCheck.isErr:
|
||||
check revRqCheck.error[2] == kQOk
|
||||
check revData.isOk == canDeleteOk
|
||||
|
||||
if canDeleteOk:
|
||||
check fwdData.value.data == revData.value.data
|
||||
check fwdRq.len == revRq.len
|
||||
check seen.len + fwdRq.len + fwdRej.len == keyList.len
|
||||
|
||||
# --------------------------------------
|
||||
|
||||
block:
|
||||
const groupLen = 7
|
||||
let veryNoisy = noisy and false
|
||||
|
||||
test &"Load/forward/reverse iterate {numUniqeKeys} items, "&
|
||||
&"deleting in groups of at most {groupLen}":
|
||||
|
||||
# forward ...
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
rc = rq.first
|
||||
while rc.isOK:
|
||||
let key = rc.value.key
|
||||
all.add key
|
||||
rc = rq.next(key)
|
||||
rq.addOrFlushGroupwise(groupLen, seen, key.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all
|
||||
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
for w in rq.nextKeys:
|
||||
all.add w
|
||||
rq.addOrFlushGroupwise(groupLen, seen, w.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
for w in rq.nextPairs:
|
||||
all.add w.key
|
||||
rq.addOrFlushGroupwise(groupLen, seen, w.key.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
for v in rq.nextValues:
|
||||
let w = v.fromValue.toKey
|
||||
all.add w
|
||||
rq.addOrFlushGroupwise(groupLen, seen, w.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all
|
||||
|
||||
# reverse ...
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
rc = rq.last
|
||||
while rc.isOK:
|
||||
let key = rc.value.key
|
||||
all.add key
|
||||
rc = rq.prev(key)
|
||||
rq.addOrFlushGroupwise(groupLen, seen, key.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all.reversed
|
||||
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
for w in rq.prevKeys:
|
||||
all.add w
|
||||
rq.addOrFlushGroupwise(groupLen, seen, w.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all.reversed
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
for w in rq.prevPairs:
|
||||
all.add w.key
|
||||
rq.addOrFlushGroupwise(groupLen, seen, w.key.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all.reversed
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
seen: seq[int]
|
||||
all: seq[uint]
|
||||
for v in rq.prevValues:
|
||||
let w = v.fromValue.toKey
|
||||
all.add w
|
||||
rq.addOrFlushGroupwise(groupLen, seen, w.fromKey, veryNoisy)
|
||||
check rq.verify.isOK
|
||||
check seen.len == rq.len
|
||||
check seen.len < groupLen
|
||||
check uniqueKeys == all.reversed
|
||||
|
||||
test &"Load/forward/reverse steps {numUniqeKeys} key/item consistency":
|
||||
|
||||
# forward ...
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
count = 0
|
||||
rc = rq.firstKey
|
||||
while rc.isOk:
|
||||
check uniqueKeys[count] == rc.value
|
||||
rc = rq.nextKey(rc.value)
|
||||
count.inc
|
||||
check rq.verify.isOK
|
||||
check count == uniqueKeys.len
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
count = 0
|
||||
rc = rq.first
|
||||
while rc.isOk:
|
||||
check uniqueKeys[count] == rc.value.data.fromValue.toKey
|
||||
rc = rq.next(rc.value.key)
|
||||
count.inc
|
||||
check rq.verify.isOK
|
||||
check count == uniqueKeys.len
|
||||
|
||||
# reverse ...
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
count = uniqueKeys.len
|
||||
rc = rq.lastKey
|
||||
while rc.isOk:
|
||||
count.dec
|
||||
check uniqueKeys[count] == rc.value
|
||||
rc = rq.prevKey(rc.value)
|
||||
check rq.verify.isOK
|
||||
check count == 0
|
||||
block:
|
||||
var
|
||||
rq = keyList.toQueue
|
||||
count = uniqueKeys.len
|
||||
rc = rq.last
|
||||
while rc.isOk:
|
||||
count.dec
|
||||
check uniqueKeys[count] == rc.value.data.fromValue.toKey
|
||||
rc = rq.prev(rc.value.key)
|
||||
check rq.verify.isOK
|
||||
check count == 0
|
||||
|
||||
# --------------------------------------
|
||||
|
||||
test &"Load/delete 2nd entries from either queue end until only one left":
|
||||
block:
|
||||
var rq = keyList.toQueue
|
||||
while true:
|
||||
let rc = rq.secondKey
|
||||
if rc.isErr:
|
||||
check rq.second.isErr
|
||||
break
|
||||
let key = rc.value
|
||||
check rq.second.value.data == rq[key]
|
||||
check rq.delete(key).isOK
|
||||
check rq.verify.isOK
|
||||
check rq.len == 1
|
||||
|
||||
block:
|
||||
var rq = keyList.toQueue
|
||||
while true:
|
||||
let rc = rq.beforeLastKey
|
||||
if rc.isErr:
|
||||
check rq.beforeLast.isErr
|
||||
break
|
||||
let key = rc.value
|
||||
check rq.beforeLast.value.data == rq[key]
|
||||
check rq.delete(key).isOK
|
||||
check rq.verify.isOK
|
||||
check rq.len == 1
|
||||
|
||||
# --------------------------------------
|
||||
|
||||
test &"Deep copy semantics":
|
||||
var
|
||||
rp = keyList.toQueue
|
||||
rq = rp
|
||||
let
|
||||
reduceLen = (rp.len div 3)
|
||||
check 0 < reduceLen
|
||||
check rp == rq
|
||||
for _ in 1 .. (rp.len div 3):
|
||||
let key = rp.firstKey.value
|
||||
check rp.delete(key).value.data.fromValue.toKey == key
|
||||
check rq.len == numUniqeKeys
|
||||
check rp.len + reduceLen == rq.len
|
||||
|
||||
|
||||
suite "KeyedQueue: Data queue as LRU cache":
|
||||
|
||||
test "Fill Up":
|
||||
var
|
||||
cache = newSeq[int]().toLruCache
|
||||
cExpected = keyList.toLruCache
|
||||
for w in keyList:
|
||||
var
|
||||
item = (w mod lruCacheModulo)
|
||||
reSched = cache.q.hasKey(item.toKey)
|
||||
value = cache.lruValue(item)
|
||||
queue = toSeq(cache.q.nextPairs).mapIt(it.key)
|
||||
values = toSeq(cache.q.nextPairs).mapIt(it.data)
|
||||
infoPfx = if reSched: ">>> rotate" else: "+++ append"
|
||||
noisy.say infoPfx, &"{value} => {queue}"
|
||||
check cache.q.verify.isOK
|
||||
check queue.mapIt($it) == values.mapIt($it.fromValue)
|
||||
check item.toKey == cache.q.lastKey.value
|
||||
check toSeq(cache.q.nextPairs) == toSeq(cExpected.q.nextPairs)
|
||||
|
||||
test "Deep copy semantics":
|
||||
var
|
||||
c1 = keyList.toLruCache
|
||||
c2 = c1
|
||||
|
||||
check c1 == c2
|
||||
check c1.lruValue(77) == 77.toValue
|
||||
|
||||
check c1.q.verify.isOK
|
||||
check c2.q.verify.isOK
|
||||
|
||||
noisy.say &"c1Specs: {c1.size} {c1.q.firstKey} {c1.q.lastKey} ..."
|
||||
noisy.say &"c2Specs: {c2.size} {c2.q.firstKey} {c2.q.lastKey} ..."
|
||||
|
||||
check c1 != c2
|
||||
check toSeq(c1.q.nextPairs) != toSeq(c2.q.nextPairs)
|
||||
|
||||
# --------------------
|
||||
|
||||
test "Random Access Delete":
|
||||
var
|
||||
c1 = keyList.toLruCache
|
||||
sq = toSeq(c1.q.nextPairs).mapIt(it.key.fromKey)
|
||||
s0 = sq
|
||||
inx = 5
|
||||
key = sq[5].toKey
|
||||
|
||||
sq.delete(5,5) # delete index 5 in sequence
|
||||
noisy.say &"sq: {s0} <off sq[5]({key})> {sq}"
|
||||
|
||||
check c1.q.delete(key).value.key == key
|
||||
check sq == toSeq(c1.q.nextPairs).mapIt(it.key.fromKey)
|
||||
check c1.q.verify.isOk
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -0,0 +1,140 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2019 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
|
||||
std/[algorithm, sequtils, strformat, tables],
|
||||
../stew/sorted_set,
|
||||
unittest
|
||||
|
||||
const
|
||||
keyList = [
|
||||
185, 208, 53, 54, 196, 189, 187, 117, 94, 29, 6, 173, 207, 45, 31,
|
||||
208, 127, 106, 117, 49, 40, 171, 6, 94, 84, 60, 125, 87, 168, 183,
|
||||
200, 155, 34, 27, 67, 107, 108, 223, 249, 4, 113, 9, 205, 100, 77,
|
||||
224, 19, 196, 14, 83, 145, 154, 95, 56, 236, 97, 115, 140, 134, 97,
|
||||
153, 167, 23, 17, 182, 116, 253, 32, 108, 148, 135, 169, 178, 124, 147,
|
||||
231, 236, 174, 211, 247, 22, 118, 144, 224, 68, 124, 200, 92, 63, 183,
|
||||
56, 107, 45, 180, 113, 233, 59, 246, 29, 212, 172, 161, 183, 207, 189,
|
||||
56, 198, 130, 62, 28, 53, 122]
|
||||
|
||||
let
|
||||
noisy = defined(debug)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
iterator fwdItems(sl: var SortedSet[int,int]): int =
|
||||
var rc = sl.ge(0)
|
||||
while rc.isOk:
|
||||
yield rc.value.key
|
||||
rc = sl.gt(rc.value.key)
|
||||
|
||||
iterator revItems(sl: var SortedSet[int,int]): int =
|
||||
var rc = sl.le(int.high)
|
||||
while rc.isOk:
|
||||
yield rc.value.key
|
||||
rc = sl.lt(rc.value.key)
|
||||
|
||||
iterator fwdWalk(sl: var SortedSet[int,int]): int =
|
||||
var
|
||||
w = SortedSetWalkRef[int,int].init(sl)
|
||||
rc = w.first
|
||||
while rc.isOk:
|
||||
yield rc.value.key
|
||||
rc = w.next
|
||||
w.destroy
|
||||
|
||||
iterator revWalk(sl: var SortedSet[int,int]): int =
|
||||
var
|
||||
w = SortedSetWalkRef[int,int].init(sl)
|
||||
var
|
||||
rc = w.last
|
||||
while rc.isOk:
|
||||
yield rc.value.key
|
||||
rc = w.prev
|
||||
w.destroy
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Test Runners
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
let
|
||||
numUniqeKeys = keyList.toSeq.mapIt((it,false)).toTable.len
|
||||
numKeyDups = keyList.len - numUniqeKeys
|
||||
|
||||
suite "SortedSet: Sorted list based on red-black tree":
|
||||
var
|
||||
sl = SortedSet[int,int].init
|
||||
rej: seq[int]
|
||||
|
||||
test &"Insert {keyList.len} items, reject {numKeyDups} duplicates":
|
||||
for n in keyList:
|
||||
let rc = sl.insert(n)
|
||||
if rc.isErr:
|
||||
rej.add n
|
||||
else:
|
||||
rc.value.data = -n
|
||||
let check = sl.verify
|
||||
if check.isErr:
|
||||
check check.error[1] == rbOk # force message
|
||||
check sl.len == numUniqeKeys
|
||||
check rej.len == numKeyDups
|
||||
check sl.len + rej.len == keyList.len
|
||||
|
||||
test &"Verify increasing/decreasing traversals":
|
||||
check toSeq(sl.fwdItems) == toSeq(sl.fwdWalk)
|
||||
check toSeq(sl.revItems) == toSeq(sl.revWalk)
|
||||
check toSeq(sl.fwdItems) == toSeq(sl.revWalk).reversed
|
||||
check toSeq(sl.revItems) == toSeq(sl.fwdWalk).reversed
|
||||
|
||||
# check `sLstEq()`
|
||||
block:
|
||||
var rc = sl.ge(0)
|
||||
while rc.isOk:
|
||||
check rc == sl.eq(rc.value.key)
|
||||
rc = sl.gt(rc.value.key)
|
||||
|
||||
# check `sLstThis()`
|
||||
block:
|
||||
var
|
||||
w = SortedSetWalkRef[int,int].init(sl)
|
||||
rc = w.first
|
||||
while rc.isOk:
|
||||
check rc == w.this
|
||||
rc = w.next
|
||||
w.destroy
|
||||
|
||||
test "Delete items":
|
||||
var seen: seq[int]
|
||||
let sub7 = keyList.len div 7
|
||||
for n in toSeq(countUp(0,sub7)).concat(toSeq(countUp(3*sub7,4*sub7))):
|
||||
let
|
||||
key = keyList[n]
|
||||
canDeleteOk = (key notin seen)
|
||||
|
||||
data = sl.delete(key)
|
||||
slCheck = sl.verify
|
||||
|
||||
if key notin seen:
|
||||
seen.add key
|
||||
|
||||
if slCheck.isErr:
|
||||
check slCheck.error[1] == rbOk # force message
|
||||
check data.isOk == canDeleteOk
|
||||
|
||||
if canDeleteOk:
|
||||
check data.value.key == key
|
||||
|
||||
check seen.len + sl.len + rej.len == keyList.len
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
Loading…
Reference in New Issue