From b002c1fad025c6b5fe1f6af1fbb7502dc3173beb Mon Sep 17 00:00:00 2001 From: Jordan Hrycaj Date: Thu, 4 Nov 2021 11:17:50 +0000 Subject: [PATCH] New data structures - SortedSet and KeyedQueue See the modules' documentation for more details --- .github/workflows/ci.yml | 4 +- stew/keyed_queue.nim | 885 ++++++++++++++++++++++++++++++ stew/keyed_queue/kq_debug.nim | 100 ++++ stew/sorted_set.nim | 316 +++++++++++ stew/sorted_set/rbtree_delete.nim | 138 +++++ stew/sorted_set/rbtree_desc.nim | 308 +++++++++++ stew/sorted_set/rbtree_find.nim | 232 ++++++++ stew/sorted_set/rbtree_flush.nim | 95 ++++ stew/sorted_set/rbtree_insert.nim | 131 +++++ stew/sorted_set/rbtree_reset.nim | 36 ++ stew/sorted_set/rbtree_rotate.nim | 52 ++ stew/sorted_set/rbtree_verify.nim | 195 +++++++ stew/sorted_set/rbtree_walk.nim | 288 ++++++++++ tests/all_tests.nim | 2 + tests/test_keyed_queue.nim | 543 ++++++++++++++++++ tests/test_sorted_set.nim | 140 +++++ 16 files changed, 3463 insertions(+), 2 deletions(-) create mode 100644 stew/keyed_queue.nim create mode 100644 stew/keyed_queue/kq_debug.nim create mode 100644 stew/sorted_set.nim create mode 100644 stew/sorted_set/rbtree_delete.nim create mode 100644 stew/sorted_set/rbtree_desc.nim create mode 100644 stew/sorted_set/rbtree_find.nim create mode 100644 stew/sorted_set/rbtree_flush.nim create mode 100644 stew/sorted_set/rbtree_insert.nim create mode 100644 stew/sorted_set/rbtree_reset.nim create mode 100644 stew/sorted_set/rbtree_rotate.nim create mode 100644 stew/sorted_set/rbtree_verify.nim create mode 100644 stew/sorted_set/rbtree_walk.nim create mode 100644 tests/test_keyed_queue.nim create mode 100644 tests/test_sorted_set.nim diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c30676..73d756e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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" diff --git a/stew/keyed_queue.nim b/stew/keyed_queue.nim new file mode 100644 index 0000000..a7e1b06 --- /dev/null +++ b/stew/keyed_queue.nim @@ -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 +# ------------------------------------------------------------------------------ diff --git a/stew/keyed_queue/kq_debug.nim b/stew/keyed_queue/kq_debug.nim new file mode 100644 index 0000000..99dfb5a --- /dev/null +++ b/stew/keyed_queue/kq_debug.nim @@ -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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set.nim b/stew/sorted_set.nim new file mode 100644 index 0000000..440d18d --- /dev/null +++ b/stew/sorted_set.nim @@ -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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_delete.nim b/stew/sorted_set/rbtree_delete.nim new file mode 100644 index 0000000..c602bc9 --- /dev/null +++ b/stew/sorted_set/rbtree_delete.nim @@ -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 `_. + ## + 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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_desc.nim b/stew/sorted_set/rbtree_desc.nim new file mode 100644 index 0000000..7f57e0a --- /dev/null +++ b/stew/sorted_set/rbtree_desc.nim @@ -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 `_ tree +## library was inspired by Julienne Walker's excellent tutorial, +## captured `here `_ or +## `here `_. +## The downloadable C library has been captured +## `here `_. +## +## 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 +## | +## | a / \ c +## | +## | / \ / \ +## +## 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 `_.) + 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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_find.nim b/stew/sorted_set/rbtree_find.nim new file mode 100644 index 0000000..f87a393 --- /dev/null +++ b/stew/sorted_set/rbtree_find.nim @@ -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 `_. + ## + 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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_flush.nim b/stew/sorted_set/rbtree_flush.nim new file mode 100644 index 0000000..e268125 --- /dev/null +++ b/stew/sorted_set/rbtree_flush.nim @@ -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 `_. + ## + 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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_insert.nim b/stew/sorted_set/rbtree_insert.nim new file mode 100644 index 0000000..c522384 --- /dev/null +++ b/stew/sorted_set/rbtree_insert.nim @@ -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 `_. + ## + if rbt.root.isNil: + return ok(rbt.insertRoot(key)) + rbt.insertNode(key) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_reset.nim b/stew/sorted_set/rbtree_reset.nim new file mode 100644 index 0000000..a576319 --- /dev/null +++ b/stew/sorted_set/rbtree_reset.nim @@ -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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_rotate.nim b/stew/sorted_set/rbtree_rotate.nim new file mode 100644 index 0000000..82763c1 --- /dev/null +++ b/stew/sorted_set/rbtree_rotate.nim @@ -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 diff --git a/stew/sorted_set/rbtree_verify.nim b/stew/sorted_set/rbtree_verify.nim new file mode 100644 index 0000000..15c4c53 --- /dev/null +++ b/stew/sorted_set/rbtree_verify.nim @@ -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 +# ------------------------------------------------------------------------------ diff --git a/stew/sorted_set/rbtree_walk.nim b/stew/sorted_set/rbtree_walk.nim new file mode 100644 index 0000000..8da4bcc --- /dev/null +++ b/stew/sorted_set/rbtree_walk.nim @@ -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 `_. + ## + 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 `_. + ## + 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 `_. + ## + 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 `_. + ## + 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 `_. + ## + 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 +# ------------------------------------------------------------------------------ diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 7944902..541959c 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -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 diff --git a/tests/test_keyed_queue.nim b/tests/test_keyed_queue.nim new file mode 100644 index 0000000..b5012f0 --- /dev/null +++ b/tests/test_keyed_queue.nim @@ -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} {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 +# ------------------------------------------------------------------------------ diff --git a/tests/test_sorted_set.nim b/tests/test_sorted_set.nim new file mode 100644 index 0000000..a27d6d1 --- /dev/null +++ b/tests/test_sorted_set.nim @@ -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 +# ------------------------------------------------------------------------------