Improve documentation for critbits (#16568)

This commit is contained in:
konsumlamm 2021-01-04 07:25:05 +01:00 • committed by GitHub
commit 763fef59fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -8,10 +8,32 @@
# #
## This module implements a `crit bit tree`:idx: which is an efficient ## This module implements a `crit bit tree`:idx: which is an efficient
## container for a sorted set of strings, or for a sorted mapping of strings. Based on the excellent paper ## container for a sorted set of strings, or for a sorted mapping of strings. Based on the
## by Adam Langley. ## [excellent paper by Adam Langley](https://www.imperialviolet.org/binary/critbit.pdf).
## (A crit bit tree is a form of `radix tree`:idx: or `patricia trie`:idx:.) ## (A crit bit tree is a form of `radix tree`:idx: or `patricia trie`:idx:.)
runnableExamples:
from sequtils import toSeq
var critbitAsSet: CritBitTree[void] = ["kitten", "puppy"].toCritBitTree
doAssert critbitAsSet.len == 2
critbitAsSet.incl("")
doAssert "" in critbitAsSet
critbitAsSet.excl("")
doAssert "" notin critbitAsSet
doAssert toSeq(critbitAsSet.items) == @["kitten", "puppy"]
let same = ["puppy", "kitten", "puppy"].toCritBitTree
doAssert toSeq(same.keys) == toSeq(critbitAsSet.keys)
var critbitAsDict: CritBitTree[int] = {"key1": 42}.toCritBitTree
doAssert critbitAsDict.len == 1
critbitAsDict["key2"] = 0
doAssert "key2" in critbitAsDict
doAssert critbitAsDict["key2"] == 0
critbitAsDict.excl("key1")
doAssert "key1" notin critbitAsDict
doAssert toSeq(critbitAsDict.pairs) == @[("key2", 0)]
import std/private/since import std/private/since
type type
@ -28,17 +50,15 @@ type
Node[T] = ref NodeObj[T] Node[T] = ref NodeObj[T]
CritBitTree*[T] = object ## The crit bit tree can either be used CritBitTree*[T] = object ## The crit bit tree can either be used
## as a mapping from strings to ## as a mapping from strings to
## some type ``T`` or as a set of ## some type `T` or as a set of
## strings if ``T`` is void. ## strings if `T` is `void`.
root: Node[T] root: Node[T]
count: int count: int
func len*[T](c: CritBitTree[T]): int {.inline.} = func len*[T](c: CritBitTree[T]): int {.inline.} =
## Returns the number of elements in `c` in O(1). ## Returns the number of elements in `c` in O(1).
runnableExamples: runnableExamples:
var c: CritBitTree[void] let c = ["key1", "key2"].toCritBitTree
incl(c, "key1")
incl(c, "key2")
doAssert c.len == 2 doAssert c.len == 2
result = c.count result = c.count
@ -144,7 +164,7 @@ proc excl*[T](c: var CritBitTree[T], key: string) =
## Removes `key` (and its associated value) from the set `c`. ## Removes `key` (and its associated value) from the set `c`.
## If the `key` does not exist, nothing happens. ## If the `key` does not exist, nothing happens.
## ##
## See also: ## **See also:**
## * `incl proc <#incl,CritBitTree[void],string>`_ ## * `incl proc <#incl,CritBitTree[void],string>`_
## * `incl proc <#incl,CritBitTree[T],string,T>`_ ## * `incl proc <#incl,CritBitTree[T],string,T>`_
runnableExamples: runnableExamples:
@ -157,9 +177,9 @@ proc excl*[T](c: var CritBitTree[T], key: string) =
proc missingOrExcl*[T](c: var CritBitTree[T], key: string): bool = proc missingOrExcl*[T](c: var CritBitTree[T], key: string): bool =
## Returns true if `c` does not contain the given `key`. If the key ## Returns true if `c` does not contain the given `key`. If the key
## does exist, c.excl(key) is performed. ## does exist, `c.excl(key)` is performed.
## ##
## See also: ## **See also:**
## * `excl proc <#excl,CritBitTree[T],string>`_ ## * `excl proc <#excl,CritBitTree[T],string>`_
## * `containsOrIncl proc <#containsOrIncl,CritBitTree[T],string,T>`_ ## * `containsOrIncl proc <#containsOrIncl,CritBitTree[T],string,T>`_
## * `containsOrIncl proc <#containsOrIncl,CritBitTree[void],string>`_ ## * `containsOrIncl proc <#containsOrIncl,CritBitTree[void],string>`_
@ -178,10 +198,10 @@ proc missingOrExcl*[T](c: var CritBitTree[T], key: string): bool =
result = c.count == oldCount result = c.count == oldCount
proc containsOrIncl*[T](c: var CritBitTree[T], key: string, val: T): bool = proc containsOrIncl*[T](c: var CritBitTree[T], key: string, val: T): bool =
## Returns true if `c` contains the given `key`. If the key does not exist ## Returns true if `c` contains the given `key`. If the key does not exist,
## ``c[key] = val`` is performed. ## `c[key] = val` is performed.
## ##
## See also: ## **See also:**
## * `incl proc <#incl,CritBitTree[void],string>`_ ## * `incl proc <#incl,CritBitTree[void],string>`_
## * `incl proc <#incl,CritBitTree[T],string,T>`_ ## * `incl proc <#incl,CritBitTree[T],string,T>`_
## * `containsOrIncl proc <#containsOrIncl,CritBitTree[void],string>`_ ## * `containsOrIncl proc <#containsOrIncl,CritBitTree[void],string>`_
@ -204,10 +224,10 @@ proc containsOrIncl*[T](c: var CritBitTree[T], key: string, val: T): bool =
if not result: n.val = val if not result: n.val = val
proc containsOrIncl*(c: var CritBitTree[void], key: string): bool = proc containsOrIncl*(c: var CritBitTree[void], key: string): bool =
## Returns true if `c` contains the given `key`. If the key does not exist ## Returns true if `c` contains the given `key`. If the key does not exist,
## it is inserted into `c`. ## it is inserted into `c`.
## ##
## See also: ## **See also:**
## * `incl proc <#incl,CritBitTree[void],string>`_ ## * `incl proc <#incl,CritBitTree[void],string>`_
## * `incl proc <#incl,CritBitTree[T],string,T>`_ ## * `incl proc <#incl,CritBitTree[T],string,T>`_
## * `containsOrIncl proc <#containsOrIncl,CritBitTree[T],string,T>`_ ## * `containsOrIncl proc <#containsOrIncl,CritBitTree[T],string,T>`_
@ -240,7 +260,7 @@ proc inc*(c: var CritBitTree[int]; key: string, val: int = 1) =
proc incl*(c: var CritBitTree[void], key: string) = proc incl*(c: var CritBitTree[void], key: string) =
## Includes `key` in `c`. ## Includes `key` in `c`.
## ##
## See also: ## **See also:**
## * `excl proc <#excl,CritBitTree[T],string>`_ ## * `excl proc <#excl,CritBitTree[T],string>`_
## * `incl proc <#incl,CritBitTree[T],string,T>`_ ## * `incl proc <#incl,CritBitTree[T],string,T>`_
runnableExamples: runnableExamples:
@ -253,7 +273,7 @@ proc incl*(c: var CritBitTree[void], key: string) =
proc incl*[T](c: var CritBitTree[T], key: string, val: T) = proc incl*[T](c: var CritBitTree[T], key: string, val: T) =
## Inserts `key` with value `val` into `c`. ## Inserts `key` with value `val` into `c`.
## ##
## See also: ## **See also:**
## * `excl proc <#excl,CritBitTree[T],string>`_ ## * `excl proc <#excl,CritBitTree[T],string>`_
## * `incl proc <#incl,CritBitTree[void],string>`_ ## * `incl proc <#incl,CritBitTree[void],string>`_
runnableExamples: runnableExamples:
@ -265,16 +285,11 @@ proc incl*[T](c: var CritBitTree[T], key: string, val: T) =
n.val = val n.val = val
proc `[]=`*[T](c: var CritBitTree[T], key: string, val: T) = proc `[]=`*[T](c: var CritBitTree[T], key: string, val: T) =
## Puts a (key, value)-pair into `t`. ## Alias for `incl <#incl,CritBitTree[T],string,T>`_.
## ##
## See also: ## **See also:**
## * `[] proc <#[],CritBitTree[T],string>`_ ## * `[] proc <#[],CritBitTree[T],string>`_
## * `[] proc <#[],CritBitTree[T],string_2>`_ ## * `[] proc <#[],CritBitTree[T],string_2>`_
runnableExamples:
var c: CritBitTree[int]
c["key"] = 42
doAssert c["key"] == 42
var n = rawInsert(c, key) var n = rawInsert(c, key)
n.val = val n.val = val
@ -286,20 +301,20 @@ template get[T](c: CritBitTree[T], key: string): T =
n.val n.val
func `[]`*[T](c: CritBitTree[T], key: string): T {.inline.} = func `[]`*[T](c: CritBitTree[T], key: string): T {.inline.} =
## Retrieves the value at ``c[key]``. If `key` is not in `t`, the ## Retrieves the value at `c[key]`. If `key` is not in `t`, the
## ``KeyError`` exception is raised. One can check with ``hasKey`` whether ## `KeyError` exception is raised. One can check with `hasKey` whether
## the key exists. ## the key exists.
## ##
## See also: ## **See also:**
## * `[] proc <#[],CritBitTree[T],string_2>`_ ## * `[] proc <#[],CritBitTree[T],string_2>`_
## * `[]= proc <#[]=,CritBitTree[T],string,T>`_ ## * `[]= proc <#[]=,CritBitTree[T],string,T>`_
get(c, key) get(c, key)
func `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline.} = func `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline.} =
## Retrieves the value at ``c[key]``. The value can be modified. ## Retrieves the value at `c[key]`. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised. ## If `key` is not in `t`, the `KeyError` exception is raised.
## ##
## See also: ## **See also:**
## * `[] proc <#[],CritBitTree[T],string>`_ ## * `[] proc <#[],CritBitTree[T],string>`_
## * `[]= proc <#[]=,CritBitTree[T],string,T>`_ ## * `[]= proc <#[]=,CritBitTree[T],string,T>`_
get(c, key) get(c, key)
@ -320,27 +335,24 @@ iterator leaves[T](n: Node[T]): Node[T] =
iterator keys*[T](c: CritBitTree[T]): string = iterator keys*[T](c: CritBitTree[T]): string =
## Yields all keys in lexicographical order. ## Yields all keys in lexicographical order.
runnableExamples: runnableExamples:
var c: CritBitTree[int] from sequtils import toSeq
c["key1"] = 1
c["key2"] = 2 let c = {"key1": 1, "key2": 2}.toCritBitTree
var keys: seq[string] doAssert toSeq(c.keys) == @["key1", "key2"]
for key in c.keys:
keys.add(key)
doAssert keys == @["key1", "key2"]
for x in leaves(c.root): yield x.key for x in leaves(c.root): yield x.key
iterator values*[T](c: CritBitTree[T]): T = iterator values*[T](c: CritBitTree[T]): T =
## Yields all values of `c` in the lexicographical order of the ## Yields all values of `c` in the lexicographical order of the
## corresponding keys. ## corresponding keys.
##
## **See also:**
## * `mvalues iterator <#mvalues.i,CritBitTree[T]>`_
runnableExamples: runnableExamples:
var c: CritBitTree[int] from sequtils import toSeq
c["key1"] = 1
c["key2"] = 2 let c = {"key1": 1, "key2": 2}.toCritBitTree
var vals: seq[int] doAssert toSeq(c.values) == @[1, 2]
for val in c.values:
vals.add(val)
doAssert vals == @[1, 2]
for x in leaves(c.root): yield x.val for x in leaves(c.root): yield x.val
@ -348,40 +360,33 @@ iterator mvalues*[T](c: var CritBitTree[T]): var T =
## Yields all values of `c` in the lexicographical order of the ## Yields all values of `c` in the lexicographical order of the
## corresponding keys. The values can be modified. ## corresponding keys. The values can be modified.
## ##
## See also: ## **See also:**
## * `values iterator <#values.i,CritBitTree[T]>`_ ## * `values iterator <#values.i,CritBitTree[T]>`_
for x in leaves(c.root): yield x.val for x in leaves(c.root): yield x.val
iterator items*[T](c: CritBitTree[T]): string = iterator items*[T](c: CritBitTree[T]): string =
## Yields all keys in lexicographical order. ## Alias for `keys <#keys.i,CritBitTree[T]>`_.
runnableExamples:
var c: CritBitTree[int]
c["key1"] = 1
c["key2"] = 2
var keys: seq[string]
for key in c.items:
keys.add(key)
doAssert keys == @["key1", "key2"]
for x in leaves(c.root): yield x.key for x in leaves(c.root): yield x.key
iterator pairs*[T](c: CritBitTree[T]): tuple[key: string, val: T] = iterator pairs*[T](c: CritBitTree[T]): tuple[key: string, val: T] =
## Yields all (key, value)-pairs of `c`. ## Yields all `(key, value)`-pairs of `c` in the lexicographical order of the
## corresponding keys.
##
## **See also:**
## * `mpairs iterator <#mpairs.i,CritBitTree[T]>`_
runnableExamples: runnableExamples:
var c: CritBitTree[int] from sequtils import toSeq
c["key1"] = 1
c["key2"] = 2 let c = {"key1": 1, "key2": 2}.toCritBitTree
var ps: seq[tuple[key: string, val: int]] doAssert toSeq(c.pairs) == @[(key: "key1", val: 1), (key: "key2", val: 2)]
for p in c.pairs:
ps.add(p)
doAssert ps == @[(key: "key1", val: 1), (key: "key2", val: 2)]
for x in leaves(c.root): yield (x.key, x.val) for x in leaves(c.root): yield (x.key, x.val)
iterator mpairs*[T](c: var CritBitTree[T]): tuple[key: string, val: var T] = iterator mpairs*[T](c: var CritBitTree[T]): tuple[key: string, val: var T] =
## Yields all (key, value)-pairs of `c`. The yielded values can be modified. ## Yields all `(key, value)`-pairs of `c` in the lexicographical order of the
## corresponding keys. The yielded values can be modified.
## ##
## See also: ## **See also:**
## * `pairs iterator <#pairs.i,CritBitTree[T]>`_ ## * `pairs iterator <#pairs.i,CritBitTree[T]>`_
for x in leaves(c.root): yield (x.key, x.val) for x in leaves(c.root): yield (x.key, x.val)
@ -401,33 +406,14 @@ proc allprefixedAux[T](c: CritBitTree[T], key: string;
if i >= p.key.len or p.key[i] != key[i]: return if i >= p.key.len or p.key[i] != key[i]: return
result = top result = top
iterator itemsWithPrefix*[T](c: CritBitTree[T], prefix: string;
longestMatch = false): string =
## Yields all keys starting with `prefix`. If `longestMatch` is true,
## the longest match is returned, it doesn't have to be a complete match then.
runnableExamples:
var c: CritBitTree[int]
c["key1"] = 42
c["key2"] = 43
var keys: seq[string]
for key in c.itemsWithPrefix("key"):
keys.add(key)
doAssert keys == @["key1", "key2"]
let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield x.key
iterator keysWithPrefix*[T](c: CritBitTree[T], prefix: string; iterator keysWithPrefix*[T](c: CritBitTree[T], prefix: string;
longestMatch = false): string = longestMatch = false): string =
## Yields all keys starting with `prefix`. ## Yields all keys starting with `prefix`.
runnableExamples: runnableExamples:
var c: CritBitTree[int] from sequtils import toSeq
c["key1"] = 42
c["key2"] = 43 let c = {"key1": 42, "key2": 43}.toCritBitTree
var keys: seq[string] doAssert toSeq(c.keysWithPrefix("key")) == @["key1", "key2"]
for key in c.keysWithPrefix("key"):
keys.add(key)
doAssert keys == @["key1", "key2"]
let top = allprefixedAux(c, prefix, longestMatch) let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield x.key for x in leaves(top): yield x.key
@ -436,14 +422,14 @@ iterator valuesWithPrefix*[T](c: CritBitTree[T], prefix: string;
longestMatch = false): T = longestMatch = false): T =
## Yields all values of `c` starting with `prefix` of the ## Yields all values of `c` starting with `prefix` of the
## corresponding keys. ## corresponding keys.
##
## **See also:**
## * `mvaluesWithPrefix iterator <#mvaluesWithPrefix.i,CritBitTree[T],string>`_
runnableExamples: runnableExamples:
var c: CritBitTree[int] from sequtils import toSeq
c["key1"] = 42
c["key2"] = 43 let c = {"key1": 42, "key2": 43}.toCritBitTree
var vals: seq[int] doAssert toSeq(c.valuesWithPrefix("key")) == @[42, 43]
for val in c.valuesWithPrefix("key"):
vals.add(val)
doAssert vals == @[42, 43]
let top = allprefixedAux(c, prefix, longestMatch) let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield x.val for x in leaves(top): yield x.val
@ -453,23 +439,29 @@ iterator mvaluesWithPrefix*[T](c: var CritBitTree[T], prefix: string;
## Yields all values of `c` starting with `prefix` of the ## Yields all values of `c` starting with `prefix` of the
## corresponding keys. The values can be modified. ## corresponding keys. The values can be modified.
## ##
## See also: ## **See also:**
## * `valuesWithPrefix iterator <#valuesWithPrefix.i,CritBitTree[T],string>`_ ## * `valuesWithPrefix iterator <#valuesWithPrefix.i,CritBitTree[T],string>`_
let top = allprefixedAux(c, prefix, longestMatch) let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield x.val for x in leaves(top): yield x.val
iterator itemsWithPrefix*[T](c: CritBitTree[T], prefix: string;
longestMatch = false): string =
## Alias for `keysWithPrefix <#keysWithPrefix.i,CritBitTree[T],string>`_.
let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield x.key
iterator pairsWithPrefix*[T](c: CritBitTree[T], iterator pairsWithPrefix*[T](c: CritBitTree[T],
prefix: string; prefix: string;
longestMatch = false): tuple[key: string, val: T] = longestMatch = false): tuple[key: string, val: T] =
## Yields all (key, value)-pairs of `c` starting with `prefix`. ## Yields all (key, value)-pairs of `c` starting with `prefix`.
##
## **See also:**
## * `mpairsWithPrefix iterator <#mpairsWithPrefix.i,CritBitTree[T],string>`_
runnableExamples: runnableExamples:
var c: CritBitTree[int] from sequtils import toSeq
c["key1"] = 42
c["key2"] = 43 let c = {"key1": 42, "key2": 43}.toCritBitTree
var ps: seq[tuple[key: string, val: int]] doAssert toSeq(c.pairsWithPrefix("key")) == @[(key: "key1", val: 42), (key: "key2", val: 43)]
for p in c.pairsWithPrefix("key"):
ps.add(p)
doAssert ps == @[(key: "key1", val: 42), (key: "key2", val: 43)]
let top = allprefixedAux(c, prefix, longestMatch) let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield (x.key, x.val) for x in leaves(top): yield (x.key, x.val)
@ -480,16 +472,19 @@ iterator mpairsWithPrefix*[T](c: var CritBitTree[T],
## Yields all (key, value)-pairs of `c` starting with `prefix`. ## Yields all (key, value)-pairs of `c` starting with `prefix`.
## The yielded values can be modified. ## The yielded values can be modified.
## ##
## See also: ## **See also:**
## * `pairsWithPrefix iterator <#pairsWithPrefix.i,CritBitTree[T],string>`_ ## * `pairsWithPrefix iterator <#pairsWithPrefix.i,CritBitTree[T],string>`_
let top = allprefixedAux(c, prefix, longestMatch) let top = allprefixedAux(c, prefix, longestMatch)
for x in leaves(top): yield (x.key, x.val) for x in leaves(top): yield (x.key, x.val)
func `$`*[T](c: CritBitTree[T]): string = func `$`*[T](c: CritBitTree[T]): string =
## Turns `c` into a string representation. Example outputs: ## Turns `c` into a string representation.
## ``{keyA: value, keyB: value}``, ``{:}`` runnableExamples:
## If `T` is void the outputs look like: doAssert $CritBitTree[int].default == "{:}"
## ``{keyA, keyB}``, ``{}``. doAssert $toCritBitTree({"key1": 1, "key2": 2}) == """{"key1": 1, "key2": 2}"""
doAssert $CritBitTree[void].default == "{}"
doAssert $toCritBitTree(["key1", "key2"]) == """{"key1", "key2"}"""
if c.len == 0: if c.len == 0:
when T is void: when T is void:
result = "{}" result = "{}"
@ -516,7 +511,7 @@ func `$`*[T](c: CritBitTree[T]): string =
result.add("}") result.add("}")
func commonPrefixLen*[T](c: CritBitTree[T]): int {.inline, since((1, 3)).} = func commonPrefixLen*[T](c: CritBitTree[T]): int {.inline, since((1, 3)).} =
## Returns longest common prefix length of all keys of `c`. ## Returns the length of the longest common prefix of all keys in `c`.
## If `c` is empty, returns 0. ## If `c` is empty, returns 0.
runnableExamples: runnableExamples:
var c: CritBitTree[void] var c: CritBitTree[void]
@ -536,35 +531,12 @@ func toCritBitTree*[T](pairs: openArray[(string, T)]): CritBitTree[T] {.since: (
runnableExamples: runnableExamples:
doAssert {"a": "0", "b": "1", "c": "2"}.toCritBitTree is CritBitTree[string] doAssert {"a": "0", "b": "1", "c": "2"}.toCritBitTree is CritBitTree[string]
doAssert {"a": 0, "b": 1, "c": 2}.toCritBitTree is CritBitTree[int] doAssert {"a": 0, "b": 1, "c": 2}.toCritBitTree is CritBitTree[int]
for item in pairs: result.incl item[0], item[1] for item in pairs: result.incl item[0], item[1]
func toCritBitTree*(items: openArray[string]): CritBitTree[void] {.since: (1, 3).} = func toCritBitTree*(items: openArray[string]): CritBitTree[void] {.since: (1, 3).} =
## Creates a new `CritBitTree` that contains the given `items`. ## Creates a new `CritBitTree` that contains the given `items`.
runnableExamples: runnableExamples:
doAssert ["a", "b", "c"].toCritBitTree is CritBitTree[void] doAssert ["a", "b", "c"].toCritBitTree is CritBitTree[void]
for item in items: result.incl item for item in items: result.incl item
runnableExamples:
static:
block:
var critbitAsSet: CritBitTree[void]
doAssert critbitAsSet.len == 0
incl critbitAsSet, "kitten"
doAssert critbitAsSet.len == 1
incl critbitAsSet, "puppy"
doAssert critbitAsSet.len == 2
incl critbitAsSet, "kitten"
doAssert critbitAsSet.len == 2
incl critbitAsSet, ""
doAssert critbitAsSet.len == 3
block:
var critbitAsDict: CritBitTree[int]
critbitAsDict["key"] = 42
doAssert critbitAsDict["key"] == 42
critbitAsDict["key"] = 0
doAssert critbitAsDict["key"] == 0
critbitAsDict["key"] = -int.high
doAssert critbitAsDict["key"] == -int.high
critbitAsDict["key"] = int.high
doAssert critbitAsDict["key"] == int.high