# Nimbus # Copyright (c) 2018-2024 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. {.push raises: [].} import std/tables, pkg/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] = Table[K, KeyedQueueItem[K, V]] ## Internal table type exposed for debugging. KeyedQueue*[K, V] = object ## Data queue descriptor tab*: KeyedQueueTab[K, V] ## Data table kFirst*, kLast*: K ## Doubly linked item list queue BlindValue = distinct tuple[] ## Type name is syntactic sugar, used for key-only queues KeyedQueueNV*[K] = KeyedQueue[K, BlindValue] ## Key-only queue, no values # ------------------------------------------------------------------------------ # 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 assert rq.tab.len != 0 # debugging only let first = rq.kFirst rq.tab.withValue(first, item): if rq.tab.len == 1: # item only rq.kFirst.reset rq.kLast.reset else: rq.tab.withValue(item[].kNxt, next): if rq.tab.len == 2: # item and one more next[].kNxt = item[].kNxt # node points to itself 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]) = ## Expects: rq.tab.len != 0 # Pop last item let last = rq.kLast rq.tab.withValue(last, item): if rq.tab.len == 1: # item only rq.kFirst.reset rq.kLast.reset else: rq.tab.withValue(item[].kPrv, prev): if rq.tab.len == 2: # item and one more prev[].kPrv = item[].kPrv # single node points to itself 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) = ## Expects: rq.tab.hesKey(key) if rq.kFirst == key: rq.shiftImpl elif rq.kLast == key: rq.popImpl else: rq.tab.withValue(key, item): # 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) 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) if rq.tab.len == 0: rq.kFirst = key item.kPrv = key else: if rq.kFirst == rq.kLast: rq.tab.withValue(rq.kFirst, first): first[].kPrv = key first[].kNxt = key else: rq.tab.withValue(rq.kLast, last): last[].kNxt = key item.kPrv = rq.kLast rq.kLast = key item.kNxt = item.kPrv # terminal node rq.tab[key] = move(item) 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) if rq.tab.len == 0: rq.kLast = key item.kNxt = key else: if rq.kFirst == rq.kLast: rq.tab.withValue(rq.kLast, last): last[].kNxt = key last[].kPrv = key else: rq.tab.withValue(rq.kFirst, first): first[].kPrv = key item.kNxt = rq.kFirst rq.kFirst = key item.kPrv = item.kNxt # terminal node has: nxt == prv rq.tab[key] = move(item) # ----------- proc shiftKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] = if 0 < rq.tab.len: let key = rq.kFirst rq.shiftImpl Opt.some(key) else: Opt.none(K) proc popKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] = if 0 < rq.tab.len: let key = rq.kLast rq.popImpl Opt.some(key) else: Opt.none(K) # ----------- proc firstKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] = if rq.tab.len == 0: Opt.none(K) else: Opt.some(rq.kFirst) proc secondKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] = if rq.tab.len < 2: return Opt.none(K) noKeyError("secondKeyImpl"): return Opt.some(rq.tab[rq.kFirst].kNxt) proc beforeLastKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] = if rq.tab.len < 2: return Opt.none(K) noKeyError("lastKeyImpl"): return Opt.some(rq.tab[rq.kLast].kPrv) proc lastKeyImpl[K, V](rq: var KeyedQueue[K, V]): Opt[K] = if rq.tab.len == 0: Opt.none(K) else: Opt.some(rq.kLast) proc nextKeyImpl[K, V](rq: var KeyedQueue[K, V], key: K): Opt[K] = if not rq.tab.hasKey(key) or rq.kLast == key: return Opt.none(K) noKeyError("nextKeyImpl"): return Opt.some(rq.tab[key].kNxt) proc prevKeyImpl[K, V](rq: var KeyedQueue[K, V], key: K): Opt[K] = if not rq.tab.hasKey(key) or rq.kFirst == key: return Opt.none(K) noKeyError("prevKeyImpl"): return Opt.some(rq.tab[key].kPrv) # ------------------------------------------------------------------------------ # Public functions, constructor # ------------------------------------------------------------------------------ proc init*[K, V](rq: var KeyedQueue[K, V], initSize = defaultInitialSize) = ## Optional initaliser for the queue setting the inital size of the ## underlying table object. rq.tab = initTable[K, KeyedQueueItem[K, V]](initSize) proc init*[K, V](T: type KeyedQueue[K, V], initSize = defaultInitialSize): T = ## Initaliser variant. result.init(initSize) proc init*[K](rq: var KeyedQueueNV[K], initSize = defaultInitialSize) = ## Key-only queue, no explicit values rq.tab = initTable[K, KeyedQueueItem[K, BlindValue]](initSize) proc init*[K](T: type KeyedQueueNV[K], initSize = defaultInitialSize): 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. rq.tab.withValue(key, item): return false do: 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. rq.tab.withValue(key, item): item[].data = val return true do: return false 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)` rq.tab.withValue(key, item): item[].data = val do: 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. rq.tab.withValue(key, item): return false do: 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]): Opt[KeyedQueuePair[K, V]] = ## 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. type T = KeyedQueuePair[K, V] if 0 < rq.tab.len: rq.tab.withValue(rq.kFirst, item): let res = Opt.some KeyedQueuePair[K, V](key: rq.kFirst, data: move(item[].data)) rq.shiftImpl return res Opt.none(KeyedQueuePair[K, V]) proc shiftKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] = ## Similar to `shift()` but with different return value. rq.shiftKeyImpl proc shiftValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] = ## Similar to `shift()` but with different return value. if 0 < rq.tab.len: rq.tab.withValue(rq.kFirst, item): let res = Opt.some(move(item[].data)) rq.shiftImpl return res Opt.none(V) proc pop*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] = ## 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. type T = KeyedQueuePair[K, V] if 0 < rq.tab.len: rq.tab.withValue(rq.kLast, item): let res = Opt.some T(key: rq.kLast, data: move(item[].data)) rq.popImpl return res Opt.none(T) proc popKey*[K, V](rq: var KeyedQueue[K, V]): Opt[K] = ## Similar to `pop()` but with different return value. rq.popKeyImpl proc popValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] = ## Similar to `pop()` but with different return value. if 0 < rq.tab.len: rq.tab.withValue(rq.kLast, item): let res = Opt.some move(item[].data) rq.popImpl return res Opt.none(V) proc delete*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[KeyedQueuePair[K, V]] = ## Delete the item with key `key` from the queue and returns the key-value ## item pair just deleted (if any). type T = KeyedQueuePair[K, V] rq.tab.withValue(key, item): let res = Opt.some T(key: key, data: move(item[].data)) rq.deleteImpl(key) return res Opt.none(T) 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, default(BlindValue)) 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, default(BlindValue)) template unshift*[K](rq: var KeyedQueueNV[K], key: K): bool = ## Key-only queue variant rq.prepend(key) proc shift*[K](rq: var KeyedQueueNV[K]): Opt[K] = ## Key-only queue variant rq.shiftKeyImpl proc pop*[K](rq: var KeyedQueueNV[K]): Opt[K] = ## Key-only variant of `pop()` (same as `popKey()`) 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): Opt[V] = ## Retrieve the value data stored with the argument `key` from ## the queue if there is any. if not rq.tab.hasKey(key): return Opt.none(V) noKeyError("eq"): return Opt.some(rq.tab[key].data) proc `[]`*[K, V](rq: var KeyedQueue[K, V], key: K): var V {.raises: [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): Opt[V] = ## 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. rq.tab.withValue(key, item): 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.withValue(rq.kFirst, first): first[].kPrv = first[].kNxt # term node: nxt == prv else: # Now, there are at least three entries rq.tab.withValue(rq.kFirst, first): if first[].kNxt == key: first[].kPrv = item[].kNxt # item was the 2nd one 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. rq.tab.withValue(rq.kLast, last): last[].kNxt = key 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 ## updated (i.e. set to `val`) if it is found on the queue. In that case, ## `true` is returned. ## ## Otherwise `false` is returned. ## rq.tab.withValue(key, w): w[].data = val discard rq.lruFetch key return true 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()` (or `lruUpdate()`) this function can be used to implement ## an *LRU cache*. ## ## Example: ## :: ## 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 Opt.some(rc.value) ## block: ## let rc = expensiveCalculation(key) ## if rc.isOK: ## return Opt.some(q.lruAppend(key, rc.value, queueMax)) ## Opt.none(K) ## ## Caveat: ## This fuction must always be used in combination with `lruFetch()` or ## `lruUpdate()` while making sure that there exists no key `key` on the ## queue. This function might throw an exception if this is violated. ## # Make sure that there is no such key. Otherwise the optimised `appendImpl()` # function might garble the list of links. doAssert not rq.tab.hasKey key # 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]): Opt[K] = ## 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]): Opt[K] = ## 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]): Opt[K] = ## 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]): Opt[K] = ## 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): Opt[K] = ## 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): Opt[K] = ## 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] ): Opt[K] {.gcsafe, deprecated: "use first() for key-only queue".} = rq.firstKeyImpl proc secondKey*[K]( rq: var KeyedQueueNV[K] ): Opt[K] {.gcsafe, deprecated: "use second() for key-only queue".} = rq.secondKeyImpl proc beforeLastKey*[K]( rq: var KeyedQueueNV[K] ): Opt[K] {.gcsafe, deprecated: "use beforeLast() for key-only queue".} = rq.beforeLastKeyImpl proc lastKey*[K]( rq: var KeyedQueueNV[K] ): Opt[K] {.gcsafe, deprecated: "use last() for key-only queue".} = rq.lastKeyImpl proc nextKey*[K]( rq: var KeyedQueueNV[K], key: K ): Opt[K] {.gcsafe, deprecated: "use next() for key-only queue".} = rq.nextKeyImpl(key) proc prevKey*[K]( rq: var KeyedQueueNV[K], key: K ): Opt[K] {.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]): Opt[KeyedQueuePair[K, V]] = ## Similar to `firstKey()` but with key-value item pair return value. if rq.tab.len == 0: return Opt.none(KeyedQueuePair[K, V]) noKeyError("first"): let key = rq.kFirst return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data)) proc second*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] = ## Similar to `secondKey()` but with key-value item pair return value. if rq.tab.len < 2: return Opt.none(KeyedQueuePair[K, V]) noKeyError("second"): let key = rq.tab[rq.kFirst].kNxt return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data)) proc beforeLast*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] = ## Similar to `beforeLastKey()` but with key-value item pair return value. if rq.tab.len < 2: return Opt.none(KeyedQueuePair[K, V]) noKeyError("beforeLast"): let key = rq.tab[rq.kLast].kPrv return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data)) proc last*[K, V](rq: var KeyedQueue[K, V]): Opt[KeyedQueuePair[K, V]] = ## Similar to `lastKey()` but with key-value item pair return value. if rq.tab.len == 0: return Opt.none(KeyedQueuePair[K, V]) noKeyError("last"): let key = rq.kLast return Opt.some(KeyedQueuePair[K, V](key: key, data: rq.tab[key].data)) proc next*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[KeyedQueuePair[K, V]] = ## Similar to `nextKey()` but with key-value item pair return value. if not rq.tab.hasKey(key) or rq.kLast == key: return Opt.none(KeyedQueuePair[K, V]) noKeyError("next"): let nKey = rq.tab[key].kNxt return Opt.some(KeyedQueuePair[K, V](key: nKey, data: rq.tab[nKey].data)) proc prev*[K, V](rq: var KeyedQueue[K, V], key: K): Opt[KeyedQueuePair[K, V]] = ## Similar to `prevKey()` but with key-value item pair return value. if not rq.tab.hasKey(key) or rq.kFirst == key: return Opt.none(KeyedQueuePair[K, V]) noKeyError("prev"): let pKey = rq.tab[key].kPrv return Opt.some(KeyedQueuePair[K, V](key: pKey, data: rq.tab[pKey].data)) # ------------ proc first*[K](rq: var KeyedQueueNV[K]): Opt[K] = ## Key-only queue variant rq.firstKeyImpl proc second*[K](rq: var KeyedQueueNV[K]): Opt[K] = ## Key-only queue variant rq.secondKeyImpl proc beforeLast*[K](rq: var KeyedQueueNV[K]): Opt[K] = ## Key-only queue variant rq.beforeLastKeyImpl proc last*[K](rq: var KeyedQueueNV[K]): Opt[K] = ## Key-only queue variant rq.lastKeyImpl proc next*[K](rq: var KeyedQueueNV[K], key: K): Opt[K] = ## Key-only queue variant rq.nextKeyImpl(key) proc prev*[K](rq: var KeyedQueueNV[K], key: K): Opt[K] = ## Key-only queue variant rq.nextKeyImpl(key) # ------------------------------------------------------------------------------ # Public traversal functions, data container items # ------------------------------------------------------------------------------ proc firstValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] = ## 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 Opt.none(V) noKeyError("firstValue"): return Opt.some(rq.tab[rq.kFirst].data) 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 ## 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 Opt.none(K) noKeyError("secondValue"): return Opt.some(rq.tab[rq.tab[rq.kFirst].kNxt].data) proc beforeLastValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] = ## 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 Opt.none(V) noKeyError("beforeLastValue"): return Opt.some(rq.tab[rq.tab[rq.kLast].kPrv].data) proc lastValue*[K, V](rq: var KeyedQueue[K, V]): Opt[V] = ## 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 Opt.none(V) noKeyError("lastValue"): return Opt.some(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): Opt[KeyedQueuePair[K, V]] = ## Helper, chreate `Opt.some()` result Opt.some(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 # ------------------------------------------------------------------------------