nim-stew/stew/keyed_queue.nim

887 lines
30 KiB
Nim

# Nimbus
# Copyright (c) 2018-2022 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
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
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]
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template noKeyError(info: static[string]; code: untyped) =
try:
code
except KeyError as e:
raiseAssert "Not possible (" & info & "): " & e.msg
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc shiftImpl[K,V](rq: var KeyedQueue[K,V]) =
## Expects: rq.tab.len != 0
noKeyError("shiftImpl"):
# 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]) =
## Expects: rq.tab.len != 0
# Pop last item
noKeyError("popImpl"):
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) =
## Expects: rq.tab.hesKey(key)
if rq.kFirst == key:
rq.shiftImpl
elif rq.kLast == key:
rq.popImpl
else:
noKeyError("deleteImpl"):
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) =
## Expects: not rq.tab.hasKey(key)
# Append queue item
var item = KeyedQueueItem[K,V](data: val)
noKeyError("appendImpl"):
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) =
## Expects: not rq.tab.hasKey(key)
# Prepend queue item
var item = KeyedQueueItem[K,V](data: val)
noKeyError("prependImpl"):
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] =
noKeyError("shiftKeyImpl"):
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] =
noKeyError("popKeyImpl"):
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] =
if rq.tab.len < 2:
return err()
noKeyError("secondKeyImpl"):
return ok(rq.tab[rq.kFirst].kNxt)
proc beforeLastKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] =
if rq.tab.len < 2:
return err()
noKeyError("lastKeyImpl"):
return 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] =
if not rq.tab.hasKey(key) or rq.kLast == key:
return err()
noKeyError("nextKeyImpl"):
return ok(rq.tab[key].kNxt)
proc prevKeyImpl[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] =
if not rq.tab.hasKey(key) or rq.kFirst == key:
return err()
noKeyError("prevKeyImpl"):
return 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 =
## 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 =
## Replace value for entry associated with the key argument `key`. Returns
## `true` on success, and `false` otherwise.
if rq.tab.hasKey(key):
noKeyError("replace"):
rq.tab[key].data = val
return true
proc `[]=`*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V) =
## 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):
noKeyError("[]="):
rq.tab[key].data = val
else:
rq.appendImpl(key, val)
proc prepend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool =
## 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] =
## 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:
noKeyError("shift"):
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] =
## Similar to `shift()` but with different return value.
rq.shiftKeyImpl
proc shiftValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] =
## Similar to `shift()` but with different return value.
if 0 < rq.tab.len:
noKeyError("shiftValue"):
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] =
## 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:
noKeyError("pop"):
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] =
## Similar to `pop()` but with different return value.
rq.popKeyImpl
proc popValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] =
## Similar to `pop()` but with different return value.
if 0 < rq.tab.len:
noKeyError("popValue"):
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] =
## 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):
noKeyError("delete"):
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) =
## 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 =
## 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 =
## 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] =
## Key-only queue variant
rq.shiftKeyImpl
proc shiftKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
{.gcsafe, deprecated: "use shift() for key-only queue".} =
rq.shiftKeyImpl
proc pop*[K](rq: var KeyedQueueNV[K]): Result[K,void] =
## Key-only variant of `pop()` (same as `popKey()`)
rq.popKeyImpl
proc popKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
{.gcsafe, deprecated: "use pop() for key-only queue".} =
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] =
## Retrieve the value data stored with the argument `key` from
## the queue if there is any.
if not rq.tab.hasKey(key):
return err()
noKeyError("eq"):
return 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] =
## 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.
if not rq.tab.hasKey(key):
return err()
noKeyError("lruFetch"):
let item = rq.tab[key]
if rq.kLast != key:
# Now, `key` is in the table and does not refer to the last `item`,
# so the table has at least two entries.
# unlink item
if rq.kFirst == key:
rq.kFirst = item.kNxt
rq.tab[rq.kFirst].kPrv = rq.tab[rq.kFirst].kNxt # term node: nxt == prv
else: # Now, there are at least three entries
if rq.tab[rq.kFirst].kNxt == key:
rq.tab[rq.kFirst].kPrv = item.kNxt # item was the 2nd one
rq.tab[item.kPrv].kNxt = item.kNxt
rq.tab[item.kNxt].kPrv = item.kPrv
# Re-append item, i.e. appendImpl() without adding item.
rq.tab[rq.kLast].kNxt = key
rq.tab[key].kPrv = rq.kLast
rq.kLast = key
rq.tab[key].kNxt = rq.tab[key].kPrv # term node: nxt == prv
return ok(item.data)
proc lruAppend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V; maxItems: int): V =
## 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
try:
if maxItems <= rq.tab.len:
rq.shiftImpl
# Append new value
rq.appendImpl(key, val)
return val
except KeyError:
raiseAssert "Not possible"
# ------------------------------------------------------------------------------
# Public traversal functions, fetch keys
# ------------------------------------------------------------------------------
proc firstKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] =
## 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] =
## 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] =
## 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] =
## 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] =
## 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] =
## 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]
{.gcsafe, deprecated: "use first() for key-only queue".} =
rq.firstKeyImpl
proc secondKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
{.gcsafe, deprecated: "use second() for key-only queue".} =
rq.secondKeyImpl
proc beforeLastKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
{.gcsafe, deprecated: "use beforeLast() for key-only queue".} =
rq.beforeLastKeyImpl
proc lastKey*[K](rq: var KeyedQueueNV[K]): Result[K,void]
{.gcsafe, deprecated: "use last() for key-only queue".} =
rq.lastKeyImpl
proc nextKey*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void]
{.gcsafe, deprecated: "use next() for key-only queue".} =
rq.nextKeyImpl(key)
proc prevKey*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void]
{.gcsafe, deprecated: "use prev() for key-only queue".} =
rq.nextKeyImpl(key)
# ------------------------------------------------------------------------------
# Public traversal functions, fetch key/value pairs
# ------------------------------------------------------------------------------
proc first*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] =
## Similar to `firstKey()` but with key-value item pair return value.
if rq.tab.len == 0:
return err()
noKeyError("first"):
let key = rq.kFirst
return 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] =
## Similar to `secondKey()` but with key-value item pair return value.
if rq.tab.len < 2:
return err()
noKeyError("second"):
let key = rq.tab[rq.kFirst].kNxt
return 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] =
## Similar to `beforeLastKey()` but with key-value item pair return value.
if rq.tab.len < 2:
return err()
noKeyError("beforeLast"):
let key = rq.tab[rq.kLast].kPrv
return 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] =
## Similar to `lastKey()` but with key-value item pair return value.
if rq.tab.len == 0:
return err()
noKeyError("last"):
let key = rq.kLast
return 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] =
## Similar to `nextKey()` but with key-value item pair return value.
if not rq.tab.hasKey(key) or rq.kLast == key:
return err()
noKeyError("next"):
let nKey = rq.tab[key].kNxt
return 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] =
## Similar to `prevKey()` but with key-value item pair return value.
if not rq.tab.hasKey(key) or rq.kFirst == key:
return err()
noKeyError("prev"):
let pKey = rq.tab[key].kPrv
return ok(KeyedQueuePair[K,V](key: pKey, data: rq.tab[pKey].data))
# ------------
proc first*[K](rq: var KeyedQueueNV[K]): Result[K,void] =
## Key-only queue variant
rq.firstKeyImpl
proc second*[K](rq: var KeyedQueueNV[K]): Result[K,void] =
## Key-only queue variant
rq.secondKeyImpl
proc beforeLast*[K](rq: var KeyedQueueNV[K]): Result[K,void] =
## Key-only queue variant
rq.beforeLastKeyImpl
proc last*[K](rq: var KeyedQueueNV[K]): Result[K,void] =
## Key-only queue variant
rq.lastKeyImpl
proc next*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] =
## Key-only queue variant
rq.nextKeyImpl(key)
proc prev*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] =
## 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] =
## 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()
noKeyError("firstValue"):
return ok(rq.tab[rq.kFirst].data)
proc secondValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] =
## 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()
noKeyError("secondValue"):
return ok(rq.tab[rq.tab[rq.kFirst].kNxt].data)
proc beforeLastValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] =
## 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()
noKeyError("beforeLastValue"):
return ok(rq.tab[rq.tab[rq.kLast].kPrv].data)
proc lastValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] =
## 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()
noKeyError("lastValue"):
return ok(rq.tab[rq.kLast].data)
# ------------------------------------------------------------------------------
# Public functions, miscellaneous
# ------------------------------------------------------------------------------
proc `==`*[K,V](a, b: var KeyedQueue[K,V]): bool =
## 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
noKeyError("=="):
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 =
## Getter
kqp.key
proc len*[K,V](rq: var KeyedQueue[K,V]): int =
## Returns the number of items in the queue
rq.tab.len
proc clear*[K,V](rq: var KeyedQueue[K,V]) =
## 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 =
## 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 become unpredictable.
if 0 < rq.tab.len:
var
key = rq.kFirst
loopOK = true
while loopOK:
let yKey = key
loopOK = key != rq.kLast
noKeyError("nextKeys"):
key = rq.tab[key].kNxt
yield yKey
iterator nextValues*[K,V](rq: var KeyedQueue[K,V]): V =
## 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:
var item: KeyedQueueItem[K,V]
noKeyError("nextValues"):
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] =
## 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
var item: KeyedQueueItem[K,V]
noKeyError("nextPairs"):
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 =
## 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
noKeyError("prevKeys"):
key = rq.tab[key].kPrv
yield yKey
iterator prevValues*[K,V](rq: var KeyedQueue[K,V]): V =
## 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:
var item: KeyedQueueItem[K,V]
noKeyError("prevValues"):
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] =
## 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
var item: KeyedQueueItem[K,V]
noKeyError("prevPairs"):
item = rq.tab[key]
loopOK = key != rq.kFirst
key = item.kPrv
yield KeyedQueuePair[K,V](key: yKey, data: item.data)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------