New data structures - SortedSet and KeyedQueue

See the modules' documentation for more details
This commit is contained in:
Jordan Hrycaj 2021-11-04 11:17:50 +00:00 committed by GitHub
parent 478cc6efde
commit b002c1fad0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3463 additions and 2 deletions

View File

@ -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"

885
stew/keyed_queue.nim Normal file
View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

316
stew/sorted_set.nim Normal file
View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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/Redblack_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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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
# ------------------------------------------------------------------------------

View File

@ -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

543
tests/test_keyed_queue.nim Normal file
View File

@ -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
# ------------------------------------------------------------------------------

140
tests/test_sorted_set.nim Normal file
View File

@ -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
# ------------------------------------------------------------------------------