keyed_queue: avoid copies, lookups (#220)

This change avoids many superfluous data copies and lookups by
exploiting `withValue` thus performing only one lookup with the same key
(instead of hasKey + actual lookup).

We also avoid several copies of the value which often is copied even
though only the next/prev pointers from the item are needed, for examle
during shifting and lru-appending.
This commit is contained in:
Jacek Sieka 2024-06-11 08:43:41 +02:00 committed by GitHub
parent a0a53c9116
commit 28743363ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 296 additions and 316 deletions

View File

@ -22,17 +22,14 @@
## semantics, this means that `=` performs a deep copy of the allocated queue ## 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. ## which is refered to the deep copy semantics of the underlying table driver.
import import std/tables, results
std/[math, tables],
./results
export export results
results
{.push raises: [].} {.push raises: [].}
type type
KeyedQueueItem*[K,V] = object ##\ KeyedQueueItem*[K, V] = object
## Data value container as stored in the queue. ## Data value container as stored in the queue.
## There is a special requirements for `KeyedQueueItem` terminal nodes: ## There is a special requirements for `KeyedQueueItem` terminal nodes:
## *prv == nxt* so that there is no dangling link. On the flip side, ## *prv == nxt* so that there is no dangling link. On the flip side,
@ -41,33 +38,26 @@ type
data*: V ## Some data value, can freely be modified. data*: V ## Some data value, can freely be modified.
kPrv*, kNxt*: K ## Queue links, read-only. kPrv*, kNxt*: K ## Queue links, read-only.
KeyedQueuePair*[K,V] = object ##\ KeyedQueuePair*[K, V] = object ## Key-value pair, typically used as return code.
## Key-value pair, typically used as return code.
key: K ## Sorter key (read-only for consistency with `SLstResult[K,V]`) key: K ## Sorter key (read-only for consistency with `SLstResult[K,V]`)
data*: V ## Some data value, to be modified freely data*: V ## Some data value, to be modified freely
KeyedQueueTab*[K,V] = ##\ KeyedQueueTab*[K, V] = Table[K, KeyedQueueItem[K, V]]
## Internal table type exposed for debugging. ## Internal table type exposed for debugging.
Table[K,KeyedQueueItem[K,V]]
KeyedQueue*[K,V] = object ##\ KeyedQueue*[K, V] = object ## Data queue descriptor
## Data queue descriptor
tab*: KeyedQueueTab[K, V] ## Data table tab*: KeyedQueueTab[K, V] ## Data table
kFirst*, kLast*: K ## Doubly linked item list queue kFirst*, kLast*: K ## Doubly linked item list queue
BlindValue = ##\ BlindValue = distinct tuple[] ## Type name is syntactic sugar, used for key-only queues
## Type name is syntactic sugar, used for key-only queues
distinct byte
KeyedQueueNV*[K] = ##\ KeyedQueueNV*[K] = KeyedQueue[K, BlindValue] ## Key-only queue, no values
## Key-only queue, no values
KeyedQueue[K,BlindValue]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Private helpers # Private helpers
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
template noKeyError(info: static[string]; code: untyped) = template noKeyError(info: static[string], code: untyped) =
try: try:
code code
except KeyError as e: except KeyError as e:
@ -81,180 +71,193 @@ proc shiftImpl[K,V](rq: var KeyedQueue[K,V]) =
## Expects: rq.tab.len != 0 ## Expects: rq.tab.len != 0
assert rq.tab.len != 0 # debugging only assert rq.tab.len != 0 # debugging only
noKeyError("shiftImpl"): let first = rq.kFirst
# Unqueue first item rq.tab.withValue(first, item):
let item = rq.tab[rq.kFirst] # yes, crashes if `rq.tab.len == 0` if rq.tab.len == 1: # item only
rq.tab.del(rq.kFirst)
if rq.tab.len == 0:
rq.kFirst.reset rq.kFirst.reset
rq.kLast.reset rq.kLast.reset
else: else:
rq.kFirst = item.kNxt rq.tab.withValue(item[].kNxt, next):
if rq.tab.len == 1: if rq.tab.len == 2: # item and one more
rq.tab[rq.kFirst].kNxt = rq.kFirst # node points to itself next[].kNxt = item[].kNxt # node points to itself
rq.tab[rq.kFirst].kPrv = rq.tab[rq.kFirst].kNxt # term nd has: nxt == prv next[].kPrv = next[].kNxt # term nd has: nxt == prv
rq.kFirst = item[].kNxt
do:
raiseAssert "rq.kFirst missing"
rq.tab.del(first)
proc popImpl[K, V](rq: var KeyedQueue[K, V]) = proc popImpl[K, V](rq: var KeyedQueue[K, V]) =
## Expects: rq.tab.len != 0 ## Expects: rq.tab.len != 0
# Pop last item # Pop last item
noKeyError("popImpl"): let last = rq.kLast
let item = rq.tab[rq.kLast] # yes, crashes if `rq.tab.len == 0` rq.tab.withValue(last, item):
rq.tab.del(rq.kLast) if rq.tab.len == 1: # item only
if rq.tab.len == 0:
rq.kFirst.reset rq.kFirst.reset
rq.kLast.reset rq.kLast.reset
else: else:
rq.kLast = item.kPrv rq.tab.withValue(item[].kPrv, prev):
if rq.tab.len == 1: if rq.tab.len == 2: # item and one more
rq.tab[rq.kLast].kPrv = rq.kLast # single node points to itself prev[].kPrv = item[].kPrv # single node points to itself
rq.tab[rq.kLast].kNxt = rq.tab[rq.kLast].kPrv # term node has: nxt == prv prev[].kNxt = prev[].kPrv # term node has: nxt == prv
rq.kLast = item[].kPrv
do:
raiseAssert "rq.kLast missing"
rq.tab.del(last)
proc deleteImpl[K,V](rq: var KeyedQueue[K,V]; key: K) = proc deleteImpl[K, V](rq: var KeyedQueue[K, V], key: K) =
## Expects: rq.tab.hesKey(key) ## Expects: rq.tab.hesKey(key)
if rq.kFirst == key: if rq.kFirst == key:
rq.shiftImpl rq.shiftImpl
elif rq.kLast == key: elif rq.kLast == key:
rq.popImpl rq.popImpl
else: else:
noKeyError("deleteImpl"): rq.tab.withValue(key, item):
let item = rq.tab[key] # yes, crashes if `not rq.tab.hasKey(key)` # now: 2 < rq.tab.len (otherwise rq.kFirst == key or rq.kLast == key)
rq.tab.withValue(rq.kFirst, first):
if first[].kNxt == key:
# item was the second one
first[].kPrv = item[].kNxt
rq.tab.withValue(rq.kLast, last):
if last.kPrv == key:
# item was one before last
last.kNxt = item[].kPrv
rq.tab.withValue(item[].kPrv, other):
other[].kNxt = item[].kNxt
rq.tab.withValue(item[].kNxt, other):
other[].kPrv = item[].kPrv
do:
raiseAssert "item missing"
rq.tab.del(key) rq.tab.del(key)
# now: 2 < rq.tab.len (otherwise rq.kFirst == key or rq.kLast == key) proc appendImpl[K, V](rq: var KeyedQueue[K, V], key: K, val: V) =
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) ## Expects: not rq.tab.hasKey(key)
# Append queue item # Append queue item
var item = KeyedQueueItem[K, V](data: val) var item = KeyedQueueItem[K, V](data: val)
noKeyError("appendImpl"):
if rq.tab.len == 0: if rq.tab.len == 0:
rq.kFirst = key rq.kFirst = key
item.kPrv = key item.kPrv = key
else: else:
if rq.kFirst == rq.kLast: if rq.kFirst == rq.kLast:
rq.tab[rq.kFirst].kPrv = key # first terminal node rq.tab.withValue(rq.kFirst, first):
rq.tab[rq.kLast].kNxt = key first[].kPrv = key
first[].kNxt = key
else:
rq.tab.withValue(rq.kLast, last):
last[].kNxt = key
item.kPrv = rq.kLast item.kPrv = rq.kLast
rq.kLast = key rq.kLast = key
item.kNxt = item.kPrv # terminal node item.kNxt = item.kPrv # terminal node
rq.tab[key] = item # yes, makes `verify()` fail if `rq.tab.hasKey(key)` rq.tab[key] = move(item)
proc prependImpl[K, V](rq: var KeyedQueue[K, V], key: K, val: V) =
proc prependImpl[K,V](rq: var KeyedQueue[K,V]; key: K; val: V) =
## Expects: not rq.tab.hasKey(key) ## Expects: not rq.tab.hasKey(key)
# Prepend queue item # Prepend queue item
var item = KeyedQueueItem[K, V](data: val) var item = KeyedQueueItem[K, V](data: val)
noKeyError("prependImpl"):
if rq.tab.len == 0: if rq.tab.len == 0:
rq.kLast = key rq.kLast = key
item.kNxt = key item.kNxt = key
else: else:
if rq.kFirst == rq.kLast: if rq.kFirst == rq.kLast:
rq.tab[rq.kLast].kNxt = key # first terminal node rq.tab.withValue(rq.kLast, last):
rq.tab[rq.kFirst].kPrv = key last[].kNxt = key
last[].kPrv = key
else:
rq.tab.withValue(rq.kFirst, first):
first[].kPrv = key
item.kNxt = rq.kFirst item.kNxt = rq.kFirst
rq.kFirst = key rq.kFirst = key
item.kPrv = item.kNxt # terminal node has: nxt == prv item.kPrv = item.kNxt # terminal node has: nxt == prv
rq.tab[key] = item # yes, makes `verify()` fail if `rq.tab.hasKey(key)` rq.tab[key] = move(item)
# ----------- # -----------
proc shiftKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc shiftKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
noKeyError("shiftKeyImpl"):
if 0 < rq.tab.len: if 0 < rq.tab.len:
let key = rq.kFirst let key = rq.kFirst
rq.shiftImpl rq.shiftImpl
return ok(key) Opt.some(key)
err() else:
Opt.none(K)
proc popKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc popKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
noKeyError("popKeyImpl"):
if 0 < rq.tab.len: if 0 < rq.tab.len:
let key = rq.kLast let key = rq.kLast
rq.popImpl rq.popImpl
return ok(key) Opt.some(key)
err() else:
Opt.none(K)
# ----------- # -----------
proc firstKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc firstKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
if rq.tab.len == 0: if rq.tab.len == 0:
return err() Opt.none(K)
ok(rq.kFirst) else:
Opt.some(rq.kFirst)
proc secondKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc secondKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
if rq.tab.len < 2: if rq.tab.len < 2:
return err() return Opt.none(K)
noKeyError("secondKeyImpl"): noKeyError("secondKeyImpl"):
return ok(rq.tab[rq.kFirst].kNxt) return Opt.some(rq.tab[rq.kFirst].kNxt)
proc beforeLastKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc beforeLastKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
if rq.tab.len < 2: if rq.tab.len < 2:
return err() return Opt.none(K)
noKeyError("lastKeyImpl"): noKeyError("lastKeyImpl"):
return ok(rq.tab[rq.kLast].kPrv) return Opt.some(rq.tab[rq.kLast].kPrv)
proc lastKeyImpl[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc lastKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
if rq.tab.len == 0: if rq.tab.len == 0:
return err() Opt.none(K)
ok(rq.kLast) else:
Opt.some(rq.kLast)
proc nextKeyImpl[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] = proc nextKeyImpl[K, V](rq: var KeyedQueue[K, V], key: K): Opt[K] =
if not rq.tab.hasKey(key) or rq.kLast == key: if not rq.tab.hasKey(key) or rq.kLast == key:
return err() return Opt.none(K)
noKeyError("nextKeyImpl"): noKeyError("nextKeyImpl"):
return ok(rq.tab[key].kNxt) return Opt.some(rq.tab[key].kNxt)
proc prevKeyImpl[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] = proc prevKeyImpl[K, V](rq: var KeyedQueue[K, V], key: K): Opt[K] =
if not rq.tab.hasKey(key) or rq.kFirst == key: if not rq.tab.hasKey(key) or rq.kFirst == key:
return err() return Opt.none(K)
noKeyError("prevKeyImpl"): noKeyError("prevKeyImpl"):
return ok(rq.tab[key].kPrv) return Opt.some(rq.tab[key].kPrv)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public functions, constructor # Public functions, constructor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc init*[K,V](rq: var KeyedQueue[K,V]; initSize = 10) = proc init*[K, V](rq: var KeyedQueue[K, V], initSize = defaultInitialSize) =
## Optional initaliser for the queue setting the inital size of the ## Optional initaliser for the queue setting the inital size of the
## underlying table object. ## underlying table object.
rq.tab = initTable[K,KeyedQueueItem[K,V]](initSize.nextPowerOfTwo) rq.tab = initTable[K, KeyedQueueItem[K, V]](initSize)
proc init*[K,V](T: type KeyedQueue[K,V]; initSize = 10): T = proc init*[K, V](T: type KeyedQueue[K, V], initSize = defaultInitialSize): T =
## Initaliser variant. ## Initaliser variant.
result.init(initSize) result.init(initSize)
proc init*[K](rq: var KeyedQueueNV[K]; initSize = 10) = proc init*[K](rq: var KeyedQueueNV[K], initSize = defaultInitialSize) =
## Key-only queue, no explicit values ## Key-only queue, no explicit values
rq.tab = initTable[K,KeyedQueueItem[K,BlindValue]](initSize.nextPowerOfTwo) rq.tab = initTable[K, KeyedQueueItem[K, BlindValue]](initSize)
proc init*[K](T: type KeyedQueueNV[K]; initSize = 10): T = proc init*[K](T: type KeyedQueueNV[K], initSize = defaultInitialSize): T =
## Initaliser variant. ## Initaliser variant.
result.init(initSize) result.init(initSize)
@ -262,61 +265,62 @@ proc init*[K](T: type KeyedQueueNV[K]; initSize = 10): T =
# Public functions, list operations # Public functions, list operations
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc append*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool = 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 ## Append new `key`. The function will succeed returning `true` unless the
## `key` argument exists in the queue, already. ## `key` argument exists in the queue, already.
## ##
## All the items on the queue different from the one just added are ## All the items on the queue different from the one just added are
## called *previous* or *left hand* items while the item just added ## called *previous* or *left hand* items while the item just added
## is the *right-most* item. ## is the *right-most* item.
if not rq.tab.hasKey(key): rq.tab.withValue(key, item):
return false
do:
rq.appendImpl(key, val) rq.appendImpl(key, val)
return true return true
template push*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool = template push*[K, V](rq: var KeyedQueue[K, V], key: K, val: V): bool =
## Same as `append()` ## Same as `append()`
rq.append(key, val) rq.append(key, val)
proc replace*[K, V](rq: var KeyedQueue[K, V], key: K, val: V): bool =
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 ## Replace value for entry associated with the key argument `key`. Returns
## `true` on success, and `false` otherwise. ## `true` on success, and `false` otherwise.
if rq.tab.hasKey(key): rq.tab.withValue(key, item):
noKeyError("replace"): item[].data = val
rq.tab[key].data = val
return true return true
do:
return false
proc `[]=`*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V) = proc `[]=`*[K, V](rq: var KeyedQueue[K, V], key: K, val: V) =
## This function provides a combined append/replace action with table ## This function provides a combined append/replace action with table
## semantics: ## semantics:
## * If the argument `key` is not in the queue yet, append the `(key,val)` ## * If the argument `key` is not in the queue yet, append the `(key,val)`
## pair as in `rq.append(key,val)` ## pair as in `rq.append(key,val)`
## * Otherwise replace the value entry of the queue item by the argument ## * Otherwise replace the value entry of the queue item by the argument
## `val` as in `rq.replace(key,val)` ## `val` as in `rq.replace(key,val)`
if rq.tab.hasKey(key): rq.tab.withValue(key, item):
noKeyError("[]="): item[].data = val
rq.tab[key].data = val do:
else:
rq.appendImpl(key, val) rq.appendImpl(key, val)
proc prepend*[K, V](rq: var KeyedQueue[K, V], key: K, val: V): bool =
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 ## Prepend new `key`. The function will succeed returning `true` unless the
## `key` argument exists in the queue, already. ## `key` argument exists in the queue, already.
## ##
## All the items on the queue different from the item just added are ## All the items on the queue different from the item just added are
## called *following* or *right hand* items while the item just added ## called *following* or *right hand* items while the item just added
## is the *left-most* item. ## is the *left-most* item.
if not rq.tab.hasKey(key): rq.tab.withValue(key, item):
return false
do:
rq.prependImpl(key, val) rq.prependImpl(key, val)
return true return true
template unshift*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool = template unshift*[K, V](rq: var KeyedQueue[K, V], key: K, val: V): bool =
## Same as `prepend()` ## Same as `prepend()`
rq.prepend(key, val) rq.prepend(key, val)
proc shift*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] =
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 ## 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 ## deleted. For a non-empty queue this function is the same as
## `rq.firstKey.value.delele`. ## `rq.firstKey.value.delele`.
@ -325,32 +329,26 @@ proc shift*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] =
## item returned and deleted is the *left-most* item. ## item returned and deleted is the *left-most* item.
type T = KeyedQueuePair[K, V] type T = KeyedQueuePair[K, V]
if 0 < rq.tab.len: if 0 < rq.tab.len:
noKeyError("shift"): rq.tab.withValue(rq.kFirst, item):
let kvp = KeyedQueuePair[K,V]( let res = Opt.some KeyedQueuePair[K, V](key: rq.kFirst, data: move(item[].data))
key: rq.kFirst,
data: rq.tab[rq.kFirst].data)
rq.shiftImpl rq.shiftImpl
when kvp is T: return res
return ok(kvp) Opt.none(KeyedQueuePair[K, V])
else:
return ok(T(kvp))
err()
proc shiftKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc shiftKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
## Similar to `shift()` but with different return value. ## Similar to `shift()` but with different return value.
rq.shiftKeyImpl rq.shiftKeyImpl
proc shiftValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] = proc shiftValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] =
## Similar to `shift()` but with different return value. ## Similar to `shift()` but with different return value.
if 0 < rq.tab.len: if 0 < rq.tab.len:
noKeyError("shiftValue"): rq.tab.withValue(rq.kFirst, item):
let val = rq.tab[rq.kFirst].data let res = Opt.some(move(item[].data))
rq.shiftImpl rq.shiftImpl
return ok(val) return res
err() Opt.none(V)
proc pop*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] =
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 ## 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 ## deleted. For a non-empty queue this function is the same as
## `rq.lastKey.value.delele`. ## `rq.lastKey.value.delele`.
@ -359,104 +357,84 @@ proc pop*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] =
## item returned and deleted is the *right-most* item. ## item returned and deleted is the *right-most* item.
type T = KeyedQueuePair[K, V] type T = KeyedQueuePair[K, V]
if 0 < rq.tab.len: if 0 < rq.tab.len:
noKeyError("pop"): rq.tab.withValue(rq.kLast, item):
let kvp = KeyedQueuePair[K,V]( let res = Opt.some T(key: rq.kLast, data: move(item[].data))
key: rq.kLast,
data: rq.tab[rq.kLast].data)
rq.popImpl rq.popImpl
when kvp is T: return res
return ok(kvp) Opt.none(T)
else:
return ok(T(kvp))
err()
proc popKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc popKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
## Similar to `pop()` but with different return value. ## Similar to `pop()` but with different return value.
rq.popKeyImpl rq.popKeyImpl
proc popValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] = proc popValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] =
## Similar to `pop()` but with different return value. ## Similar to `pop()` but with different return value.
if 0 < rq.tab.len: if 0 < rq.tab.len:
noKeyError("popValue"): rq.tab.withValue(rq.kLast, item):
let val = rq.tab[rq.kLast].data let res = Opt.some move(item[].data)
rq.popImpl rq.popImpl
return ok(val) return res
err() Opt.none(V)
proc delete*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[KeyedQueuePair[K, V]] =
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 ## Delete the item with key `key` from the queue and returns the key-value
## item pair just deleted (if any). ## item pair just deleted (if any).
if rq.tab.hasKey(key): type T = KeyedQueuePair[K, V]
noKeyError("delete"): rq.tab.withValue(key, item):
let kvp = KeyedQueuePair[K,V]( let res = Opt.some T(key: key, data: move(item[].data))
key: key,
data: rq.tab[key].data)
rq.deleteImpl(key) rq.deleteImpl(key)
return ok(kvp) return res
err()
proc del*[K,V](rq: var KeyedQueue[K,V]; key: K) = Opt.none(T)
proc del*[K, V](rq: var KeyedQueue[K, V], key: K) =
## Similar to `delete()` but without return code. ## Similar to `delete()` but without return code.
if rq.tab.hasKey(key): if rq.tab.hasKey(key):
rq.deleteImpl(key) rq.deleteImpl(key)
# -------- # --------
proc append*[K](rq: var KeyedQueueNV[K]; key: K): bool = proc append*[K](rq: var KeyedQueueNV[K], key: K): bool =
## Key-only queue variant ## Key-only queue variant
rq.append(key,BlindValue(0)) rq.append(key, default(BlindValue))
template push*[K](rq: var KeyedQueueNV[K]; key: K): bool = template push*[K](rq: var KeyedQueueNV[K], key: K): bool =
## Key-only queue variant ## Key-only queue variant
rq.append(key) rq.append(key)
proc prepend*[K](rq: var KeyedQueueNV[K], key: K): bool =
proc prepend*[K](rq: var KeyedQueueNV[K]; key: K): bool =
## Key-only queue variant ## Key-only queue variant
rq.prepend(key,BlindValue(0)) rq.prepend(key, default(BlindValue))
template unshift*[K](rq: var KeyedQueueNV[K]; key: K): bool = template unshift*[K](rq: var KeyedQueueNV[K], key: K): bool =
## Key-only queue variant ## Key-only queue variant
rq.prepend(key) rq.prepend(key)
proc shift*[K](rq: var KeyedQueueNV[K]): Opt[K] =
proc shift*[K](rq: var KeyedQueueNV[K]): Result[K,void] =
## Key-only queue variant ## Key-only queue variant
rq.shiftKeyImpl rq.shiftKeyImpl
proc shiftKey*[K](rq: var KeyedQueueNV[K]): Result[K,void] proc pop*[K](rq: var KeyedQueueNV[K]): Opt[K] =
{.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()`) ## Key-only variant of `pop()` (same as `popKey()`)
rq.popKeyImpl 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 # Public functions, fetch
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc hasKey*[K,V](rq: var KeyedQueue[K,V]; key: K): bool = proc hasKey*[K, V](rq: var KeyedQueue[K, V], key: K): bool =
## Check whether the argument `key` has been queued, already ## Check whether the argument `key` has been queued, already
rq.tab.hasKey(key) rq.tab.hasKey(key)
proc eq*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[V,void] = proc eq*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[V] =
## Retrieve the value data stored with the argument `key` from ## Retrieve the value data stored with the argument `key` from
## the queue if there is any. ## the queue if there is any.
if not rq.tab.hasKey(key): if not rq.tab.hasKey(key):
return err() return Opt.none(V)
noKeyError("eq"): noKeyError("eq"):
return ok(rq.tab[key].data) return Opt.some(rq.tab[key].data)
proc `[]`*[K,V](rq: var KeyedQueue[K,V]; key: K): V proc `[]`*[K, V](rq: var KeyedQueue[K, V], key: K): var V {.raises: [KeyError].} =
{.gcsafe,raises: [KeyError].} =
## This function provides a simplified version of the `eq()` function with ## This function provides a simplified version of the `eq()` function with
## table semantics. Note that this finction throws a `KeyError` exception ## table semantics. Note that this finction throws a `KeyError` exception
## unless the argument `key` exists in the queue. ## unless the argument `key` exists in the queue.
@ -466,38 +444,39 @@ proc `[]`*[K,V](rq: var KeyedQueue[K,V]; key: K): V
# Public functions, LRU mode # Public functions, LRU mode
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc lruFetch*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[V,void] = proc lruFetch*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[V] =
## Fetch in *last-recently-used* mode: If the argument `key` exists in the ## 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()`) ## queue, move the key-value item pair to the *right end* (see `append()`)
## of the queue and return the value associated with the key. ## of the queue and return the value associated with the key.
if not rq.tab.hasKey(key): rq.tab.withValue(key, item):
return err()
noKeyError("lruFetch"):
let item = rq.tab[key]
if rq.kLast != key: if rq.kLast != key:
# Now, `key` is in the table and does not refer to the last `item`, # Now, `key` is in the table and does not refer to the last `item`,
# so the table has at least two entries. # so the table has at least two entries.
# unlink item # unlink item
if rq.kFirst == key: if rq.kFirst == key:
rq.kFirst = item.kNxt rq.kFirst = item[].kNxt
rq.tab[rq.kFirst].kPrv = rq.tab[rq.kFirst].kNxt # term node: nxt == prv rq.tab.withValue(rq.kFirst, first):
first[].kPrv = first[].kNxt # term node: nxt == prv
else: # Now, there are at least three entries else: # Now, there are at least three entries
if rq.tab[rq.kFirst].kNxt == key: rq.tab.withValue(rq.kFirst, first):
rq.tab[rq.kFirst].kPrv = item.kNxt # item was the 2nd one if first[].kNxt == key:
rq.tab[item.kPrv].kNxt = item.kNxt first[].kPrv = item[].kNxt # item was the 2nd one
rq.tab[item.kNxt].kPrv = item.kPrv rq.tab.withValue(item[].kPrv, prev):
prev[].kNxt = item[].kNxt
rq.tab.withValue(item[].kNxt, next):
next[].kPrv = item[].kPrv
# Re-append item, i.e. appendImpl() without adding item. # Re-append item, i.e. appendImpl() without adding item.
rq.tab[rq.kLast].kNxt = key rq.tab.withValue(rq.kLast, last):
rq.tab[key].kPrv = rq.kLast last[].kNxt = key
rq.kLast = key
rq.tab[key].kNxt = rq.tab[key].kPrv # term node: nxt == prv
return ok(item.data)
proc lruUpdate*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool = item[].kPrv = rq.kLast
rq.kLast = key
item[].kNxt = item[].kPrv # term node: nxt == prv
return Opt.some(item[].data)
proc lruUpdate*[K, V](rq: var KeyedQueue[K, V], key: K, val: V): bool =
## Similar to `lruFetch()` with the difference that the item value is ## Similar to `lruFetch()` with the difference that the item value is
## updated (i.e. set to `val`) if it is found on the queue. In that case, ## updated (i.e. set to `val`) if it is found on the queue. In that case,
## `true` is returned. ## `true` is returned.
@ -505,11 +484,11 @@ proc lruUpdate*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V): bool =
## Otherwise `false` is returned. ## Otherwise `false` is returned.
## ##
rq.tab.withValue(key, w): rq.tab.withValue(key, w):
w.data = val w[].data = val
discard rq.lruFetch key discard rq.lruFetch key
return true return true
proc lruAppend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V; maxItems: int): V = 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` ## 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 ## item entries, do `shift()` out the *left-most* one. Then `append()` the
## key-value argument pair `(key,val)` to the *right end*. Together with ## key-value argument pair `(key,val)` to the *right end*. Together with
@ -527,12 +506,12 @@ proc lruAppend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V; maxItems: int): V
## block: ## block:
## let rc = q.lruFetch(key) ## let rc = q.lruFetch(key)
## if rc.isOK: ## if rc.isOK:
## return ok(rc.value) ## return Opt.some(rc.value)
## block: ## block:
## let rc = expensiveCalculation(key) ## let rc = expensiveCalculation(key)
## if rc.isOK: ## if rc.isOK:
## return ok(q.lruAppend(key, rc.value, queueMax)) ## return Opt.some(q.lruAppend(key, rc.value, queueMax))
## err() ## Opt.none(K)
## ##
## Caveat: ## Caveat:
## This fuction must always be used in combination with `lruFetch()` or ## This fuction must always be used in combination with `lruFetch()` or
@ -542,7 +521,6 @@ proc lruAppend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V; maxItems: int): V
# Make sure that there is no such key. Otherwise the optimised `appendImpl()` # Make sure that there is no such key. Otherwise the optimised `appendImpl()`
# function might garble the list of links. # function might garble the list of links.
doAssert not rq.tab.hasKey key doAssert not rq.tab.hasKey key
# Limit number of cached items # Limit number of cached items
try: try:
if maxItems <= rq.tab.len: if maxItems <= rq.tab.len:
@ -557,35 +535,35 @@ proc lruAppend*[K,V](rq: var KeyedQueue[K,V]; key: K; val: V; maxItems: int): V
# Public traversal functions, fetch keys # Public traversal functions, fetch keys
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc firstKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc firstKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
## Retrieve first key from the queue unless it is empty. ## Retrieve first key from the queue unless it is empty.
## ##
## Using the notation introduced with `rq.append` and `rq.prepend`, the ## Using the notation introduced with `rq.append` and `rq.prepend`, the
## key returned is the *left-most* one. ## key returned is the *left-most* one.
rq.firstKeyImpl rq.firstKeyImpl
proc secondKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc secondKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
## Retrieve the key next after the first key from queue unless it is empty. ## 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 ## Using the notation introduced with `rq.append` and `rq.prepend`, the
## key returned is the one ti the right of the *left-most* one. ## key returned is the one ti the right of the *left-most* one.
rq.secondKeyImpl rq.secondKeyImpl
proc beforeLastKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc beforeLastKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
## Retrieve the key just before the last one from queue unless it is empty. ## 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 ## Using the notation introduced with `rq.append` and `rq.prepend`, the
## key returned is the one to the left of the *right-most* one. ## key returned is the one to the left of the *right-most* one.
rq.beforeLastKeyImpl rq.beforeLastKeyImpl
proc lastKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] = proc lastKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] =
## Retrieve last key from queue unless it is empty. ## Retrieve last key from queue unless it is empty.
## ##
## Using the notation introduced with `rq.append` and `rq.prepend`, the ## Using the notation introduced with `rq.append` and `rq.prepend`, the
## key returned is the *right-most* one. ## key returned is the *right-most* one.
rq.lastKeyImpl rq.lastKeyImpl
proc nextKey*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] = proc nextKey*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[K] =
## Retrieve the key following the argument `key` from queue if ## Retrieve the key following the argument `key` from queue if
## there is any. ## there is any.
## ##
@ -593,7 +571,7 @@ proc nextKey*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] =
## key returned is the next one to the *right*. ## key returned is the next one to the *right*.
rq.nextKeyImpl(key) rq.nextKeyImpl(key)
proc prevKey*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] = proc prevKey*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[K] =
## Retrieve the key preceeding the argument `key` from queue if ## Retrieve the key preceeding the argument `key` from queue if
## there is any. ## there is any.
## ##
@ -603,108 +581,111 @@ proc prevKey*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[K,void] =
# ---------- # ----------
proc firstKey*[K](rq: var KeyedQueueNV[K]): Result[K,void] proc firstKey*[K](
{.gcsafe, deprecated: "use first() for key-only queue".} = rq: var KeyedQueueNV[K]
): Opt[K] {.gcsafe, deprecated: "use first() for key-only queue".} =
rq.firstKeyImpl rq.firstKeyImpl
proc secondKey*[K](rq: var KeyedQueueNV[K]): Result[K,void] proc secondKey*[K](
{.gcsafe, deprecated: "use second() for key-only queue".} = rq: var KeyedQueueNV[K]
): Opt[K] {.gcsafe, deprecated: "use second() for key-only queue".} =
rq.secondKeyImpl rq.secondKeyImpl
proc beforeLastKey*[K](rq: var KeyedQueueNV[K]): Result[K,void] proc beforeLastKey*[K](
{.gcsafe, deprecated: "use beforeLast() for key-only queue".} = rq: var KeyedQueueNV[K]
): Opt[K] {.gcsafe, deprecated: "use beforeLast() for key-only queue".} =
rq.beforeLastKeyImpl rq.beforeLastKeyImpl
proc lastKey*[K](rq: var KeyedQueueNV[K]): Result[K,void] proc lastKey*[K](
{.gcsafe, deprecated: "use last() for key-only queue".} = rq: var KeyedQueueNV[K]
): Opt[K] {.gcsafe, deprecated: "use last() for key-only queue".} =
rq.lastKeyImpl rq.lastKeyImpl
proc nextKey*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] proc nextKey*[K](
{.gcsafe, deprecated: "use next() for key-only queue".} = rq: var KeyedQueueNV[K], key: K
): Opt[K] {.gcsafe, deprecated: "use next() for key-only queue".} =
rq.nextKeyImpl(key) rq.nextKeyImpl(key)
proc prevKey*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] proc prevKey*[K](
{.gcsafe, deprecated: "use prev() for key-only queue".} = rq: var KeyedQueueNV[K], key: K
): Opt[K] {.gcsafe, deprecated: "use prev() for key-only queue".} =
rq.nextKeyImpl(key) rq.nextKeyImpl(key)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public traversal functions, fetch key/value pairs # Public traversal functions, fetch key/value pairs
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc first*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] = proc first*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] =
## Similar to `firstKey()` but with key-value item pair return value. ## Similar to `firstKey()` but with key-value item pair return value.
if rq.tab.len == 0: if rq.tab.len == 0:
return err() return Opt.none(KeyedQueuePair[K, V])
noKeyError("first"): noKeyError("first"):
let key = rq.kFirst let key = rq.kFirst
return ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data)) return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data))
proc second*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] = proc second*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] =
## Similar to `secondKey()` but with key-value item pair return value. ## Similar to `secondKey()` but with key-value item pair return value.
if rq.tab.len < 2: if rq.tab.len < 2:
return err() return Opt.none(KeyedQueuePair[K, V])
noKeyError("second"): noKeyError("second"):
let key = rq.tab[rq.kFirst].kNxt let key = rq.tab[rq.kFirst].kNxt
return ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data)) return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data))
proc beforeLast*[K,V](rq: var KeyedQueue[K,V]): proc beforeLast*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] =
Result[KeyedQueuePair[K,V],void] =
## Similar to `beforeLastKey()` but with key-value item pair return value. ## Similar to `beforeLastKey()` but with key-value item pair return value.
if rq.tab.len < 2: if rq.tab.len < 2:
return err() return Opt.none(KeyedQueuePair[K, V])
noKeyError("beforeLast"): noKeyError("beforeLast"):
let key = rq.tab[rq.kLast].kPrv let key = rq.tab[rq.kLast].kPrv
return ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data)) return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data))
proc last*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] = proc last*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] =
## Similar to `lastKey()` but with key-value item pair return value. ## Similar to `lastKey()` but with key-value item pair return value.
if rq.tab.len == 0: if rq.tab.len == 0:
return err() return Opt.none(KeyedQueuePair[K, V])
noKeyError("last"): noKeyError("last"):
let key = rq.kLast let key = rq.kLast
return ok(KeyedQueuePair[K,V](key: key, data: rq.tab[key].data)) return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data))
proc next*[K,V](rq: var KeyedQueue[K,V]; key: K): proc next*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[KeyedQueuePair[K, V]] =
Result[KeyedQueuePair[K,V],void] =
## Similar to `nextKey()` but with key-value item pair return value. ## Similar to `nextKey()` but with key-value item pair return value.
if not rq.tab.hasKey(key) or rq.kLast == key: if not rq.tab.hasKey(key) or rq.kLast == key:
return err() return Opt.none(KeyedQueuePair[K, V])
noKeyError("next"): noKeyError("next"):
let nKey = rq.tab[key].kNxt let nKey = rq.tab[key].kNxt
return ok(KeyedQueuePair[K,V](key: nKey, data: rq.tab[nKey].data)) return Opt.some(KeyedQueuePair[K, V](key: nKey, data: rq.tab[nKey].data))
proc prev*[K,V](rq: var KeyedQueue[K,V]; key: K): proc prev*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[KeyedQueuePair[K, V]] =
Result[KeyedQueuePair[K,V],void] =
## Similar to `prevKey()` but with key-value item pair return value. ## Similar to `prevKey()` but with key-value item pair return value.
if not rq.tab.hasKey(key) or rq.kFirst == key: if not rq.tab.hasKey(key) or rq.kFirst == key:
return err() return Opt.none(KeyedQueuePair[K, V])
noKeyError("prev"): noKeyError("prev"):
let pKey = rq.tab[key].kPrv let pKey = rq.tab[key].kPrv
return ok(KeyedQueuePair[K,V](key: pKey, data: rq.tab[pKey].data)) return Opt.some(KeyedQueuePair[K, V](key: pKey, data: rq.tab[pKey].data))
# ------------ # ------------
proc first*[K](rq: var KeyedQueueNV[K]): Result[K,void] = proc first*[K](rq: var KeyedQueueNV[K]): Opt[K] =
## Key-only queue variant ## Key-only queue variant
rq.firstKeyImpl rq.firstKeyImpl
proc second*[K](rq: var KeyedQueueNV[K]): Result[K,void] = proc second*[K](rq: var KeyedQueueNV[K]): Opt[K] =
## Key-only queue variant ## Key-only queue variant
rq.secondKeyImpl rq.secondKeyImpl
proc beforeLast*[K](rq: var KeyedQueueNV[K]): Result[K,void] = proc beforeLast*[K](rq: var KeyedQueueNV[K]): Opt[K] =
## Key-only queue variant ## Key-only queue variant
rq.beforeLastKeyImpl rq.beforeLastKeyImpl
proc last*[K](rq: var KeyedQueueNV[K]): Result[K,void] = proc last*[K](rq: var KeyedQueueNV[K]): Opt[K] =
## Key-only queue variant ## Key-only queue variant
rq.lastKeyImpl rq.lastKeyImpl
proc next*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] = proc next*[K](rq: var KeyedQueueNV[K], key: K): Opt[K] =
## Key-only queue variant ## Key-only queue variant
rq.nextKeyImpl(key) rq.nextKeyImpl(key)
proc prev*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] = proc prev*[K](rq: var KeyedQueueNV[K], key: K): Opt[K] =
## Key-only queue variant ## Key-only queue variant
rq.nextKeyImpl(key) rq.nextKeyImpl(key)
@ -712,47 +693,47 @@ proc prev*[K](rq: var KeyedQueueNV[K]; key: K): Result[K,void] =
# Public traversal functions, data container items # Public traversal functions, data container items
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc firstValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] = proc firstValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] =
## Retrieve first value item from the queue unless it is empty. ## Retrieve first value item from the queue unless it is empty.
## ##
## Using the notation introduced with `rq.append` and `rq.prepend`, the ## Using the notation introduced with `rq.append` and `rq.prepend`, the
## value item returned is the *left-most* one. ## value item returned is the *left-most* one.
if rq.tab.len == 0: if rq.tab.len == 0:
return err() return Opt.none(V)
noKeyError("firstValue"): noKeyError("firstValue"):
return ok(rq.tab[rq.kFirst].data) return Opt.some(rq.tab[rq.kFirst].data)
proc secondValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] = proc secondValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] =
## Retrieve the value item next to the first one from the queue unless it ## Retrieve the value item next to the first one from the queue unless it
## is empty. ## is empty.
## ##
## Using the notation introduced with `rq.append` and `rq.prepend`, the ## 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. ## value item returned is the one to the *right* of the *left-most* one.
if rq.tab.len < 2: if rq.tab.len < 2:
return err() return Opt.none(K)
noKeyError("secondValue"): noKeyError("secondValue"):
return ok(rq.tab[rq.tab[rq.kFirst].kNxt].data) return Opt.some(rq.tab[rq.tab[rq.kFirst].kNxt].data)
proc beforeLastValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] = proc beforeLastValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] =
## Retrieve the value item just before the last item from the queue ## Retrieve the value item just before the last item from the queue
## unless it is empty. ## unless it is empty.
## ##
## Using the notation introduced with `rq.append` and `rq.prepend`, the ## 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. ## value item returned is the one to the *left* of the *right-most* one.
if rq.tab.len < 2: if rq.tab.len < 2:
return err() return Opt.none(V)
noKeyError("beforeLastValue"): noKeyError("beforeLastValue"):
return ok(rq.tab[rq.tab[rq.kLast].kPrv].data) return Opt.some(rq.tab[rq.tab[rq.kLast].kPrv].data)
proc lastValue*[K,V](rq: var KeyedQueue[K,V]): Result[V,void] = proc lastValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] =
## Retrieve the last value item from the queue if there is any. ## Retrieve the last value item from the queue if there is any.
## ##
## Using the notation introduced with `rq.append` and `rq.prepend`, the ## Using the notation introduced with `rq.append` and `rq.prepend`, the
## value item returned is the *right-most* one. ## value item returned is the *right-most* one.
if rq.tab.len == 0: if rq.tab.len == 0:
return err() return Opt.none(V)
noKeyError("lastValue"): noKeyError("lastValue"):
return ok(rq.tab[rq.kLast].data) return Opt.some(rq.tab[rq.kLast].data)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public functions, miscellaneous # Public functions, miscellaneous
@ -786,10 +767,9 @@ proc clear*[K,V](rq: var KeyedQueue[K,V]) =
rq.kFirst.reset rq.kFirst.reset
rq.kLast.reset rq.kLast.reset
proc toKeyedQueueResult*[K,V](key: K; data: V): proc toKeyedQueueResult*[K, V](key: K, data: V): Opt[KeyedQueuePair[K, V]] =
Result[KeyedQueuePair[K,V],void] = ## Helper, chreate `Opt.some()` result
## Helper, chreate `ok()` result Opt.some(KeyedQueuePair[K, V](key: key, data: data))
ok(KeyedQueuePair[K,V](key: key, data: data))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public iterators # Public iterators