Initialized collections (#11094)

* tables: initialized by default
* sets: initialized by default
* DRY: extract shared functionality
* add a changelog entry
* fix errors
* don't test include files
* make it work for sharedtables
* fix discovered bugs
* add exhaustive tests
This commit is contained in:
Miran 2019-04-29 08:13:53 +02:00 • committed by Andreas Rumpf
commit 737fff5902
8 changed files with 660 additions and 422 deletions

View file

@ -97,6 +97,7 @@
- Custom types that should be supported by `strformat` (&) now need an - Custom types that should be supported by `strformat` (&) now need an
explicit overload of `formatValue`. explicit overload of `formatValue`.
#### Breaking changes in the compiler #### Breaking changes in the compiler
- The compiler now implements the "generic symbol prepass" for `when` statements - The compiler now implements the "generic symbol prepass" for `when` statements
@ -113,6 +114,7 @@ proc enumToString*(enums: openArray[enum]): string =
``` ```
- ``discard x`` is now illegal when `x` is a function symbol. - ``discard x`` is now illegal when `x` is a function symbol.
- Implicit imports via ``--import: module`` in a config file are now restricted - Implicit imports via ``--import: module`` in a config file are now restricted
to the main package. to the main package.
@ -188,6 +190,7 @@ proc enumToString*(enums: openArray[enum]): string =
differently. differently.
- `securehash` is moved to `lib/deprecated` - `securehash` is moved to `lib/deprecated`
- The switch ``-d:useWinAnsi`` is not supported anymore. - The switch ``-d:useWinAnsi`` is not supported anymore.
@ -211,14 +214,19 @@ proc enumToString*(enums: openArray[enum]): string =
it's more recognizable and allows tools like github to recognize it as Nim, it's more recognizable and allows tools like github to recognize it as Nim,
see [#9647](https://github.com/nim-lang/Nim/issues/9647). see [#9647](https://github.com/nim-lang/Nim/issues/9647).
The previous extension will continue to work. The previous extension will continue to work.
- Pragma syntax is now consistent. Previous syntax where type pragmas did not - Pragma syntax is now consistent. Previous syntax where type pragmas did not
follow the type name is now deprecated. Also pragma before generic parameter follow the type name is now deprecated. Also pragma before generic parameter
list is deprecated to be consistent with how pragmas are used with a proc. See list is deprecated to be consistent with how pragmas are used with a proc. See
[#8514](https://github.com/nim-lang/Nim/issues/8514) and [#8514](https://github.com/nim-lang/Nim/issues/8514) and
[#1872](https://github.com/nim-lang/Nim/issues/1872) for further details. [#1872](https://github.com/nim-lang/Nim/issues/1872) for further details.
- Hash sets and tables are initialized by default. The explicit `initHashSet`,
`initTable`, etc. are not needed anymore.
### Tool changes ### Tool changes
- `jsondoc` now include a `moduleDescription` field with the module - `jsondoc` now include a `moduleDescription` field with the module
description. `jsondoc0` shows comments as it's own objects as shown in the description. `jsondoc0` shows comments as it's own objects as shown in the
documentation. documentation.
@ -227,6 +235,7 @@ proc enumToString*(enums: openArray[enum]): string =
### Compiler changes ### Compiler changes
- The deprecated `fmod` proc is now unavailable on the VM'. - The deprecated `fmod` proc is now unavailable on the VM'.
- A new `--outdir` option was added. - A new `--outdir` option was added.
- The compiled JavaScript file for the project produced by executing `nim js` - The compiled JavaScript file for the project produced by executing `nim js`
@ -235,4 +244,5 @@ proc enumToString*(enums: openArray[enum]): string =
The compiler also provides a new more flexible API for handling the The compiler also provides a new more flexible API for handling the
hot code reloading events in the code. hot code reloading events in the code.
### Bugfixes ### Bugfixes

View file

@ -0,0 +1,68 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# An ``include`` file which contains common code for
# hash sets and tables.
const
growthFactor = 2
when not defined(nimHasDefault):
template default[T](t: typedesc[T]): T =
var v: T
v
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
template rawGetKnownHCImpl() {.dirty.} =
if t.dataLen == 0:
return -1
var h: Hash = hc and maxHash(t) # start with real hash value
while isFilled(t.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A.
if t.data[h].hcode == hc and t.data[h].key == key:
return h
h = nextTry(h, maxHash(t))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
proc rawGetKnownHC[X, A](t: X, key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
template genHashImpl(key, hc: typed) =
hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
template genHash(key: typed): Hash =
var res: Hash
genHashImpl(key, res)
res
template rawGetImpl() {.dirty.} =
genHashImpl(key, hc)
rawGetKnownHCImpl()
proc rawGet[X, A](t: X, key: A, hc: var Hash): int {.inline.} =
rawGetImpl()

View file

@ -0,0 +1,153 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# An ``include`` file for the different hash set implementations.
template maxHash(t): untyped = high(t.data)
template dataLen(t): untyped = len(t.data)
include hashcommon
template initImpl(s: typed, size: int) =
assert isPowerOfTwo(size)
when s is OrderedSet:
s.first = -1
s.last = -1
s.counter = 0
newSeq(s.data, size)
template rawInsertImpl() {.dirty.} =
if data.len == 0:
initImpl(s, defaultInitialSize)
data[h].key = key
data[h].hcode = hc
proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A,
hc: Hash, h: Hash) =
rawInsertImpl()
proc enlarge[A](s: var HashSet[A]) =
var n: KeyValuePairSeq[A]
newSeq(n, len(s.data) * growthFactor)
swap(s.data, n) # n is now old seq
for i in countup(0, high(n)):
if isFilled(n[i].hcode):
var j = -1 - rawGetKnownHC(s, n[i].key, n[i].hcode)
rawInsert(s, s.data, n[i].key, n[i].hcode, j)
template inclImpl() {.dirty.} =
if s.data.len == 0:
initImpl(s, defaultInitialSize)
var hc: Hash
var index = rawGet(s, key, hc)
if index < 0:
if mustRehash(len(s.data), s.counter):
enlarge(s)
index = rawGetKnownHC(s, key, hc)
rawInsert(s, s.data, key, hc, -1 - index)
inc(s.counter)
template containsOrInclImpl() {.dirty.} =
if s.data.len == 0:
initImpl(s, defaultInitialSize)
var hc: Hash
var index = rawGet(s, key, hc)
if index >= 0:
result = true
else:
if mustRehash(len(s.data), s.counter):
enlarge(s)
index = rawGetKnownHC(s, key, hc)
rawInsert(s, s.data, key, hc, -1 - index)
inc(s.counter)
template doWhile(a, b) =
while true:
b
if not a: break
proc exclImpl[A](s: var HashSet[A], key: A) : bool {. inline .} =
var hc: Hash
var i = rawGet(s, key, hc)
var msk = high(s.data)
result = true
if i >= 0:
result = false
dec(s.counter)
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
s.data[i].hcode = 0 # mark current EMPTY
s.data[i].key = default(type(s.data[i].key))
doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
i = (i + 1) and msk # increment mod table size
if isEmpty(s.data[i].hcode): # end of collision cluster; So all done
return
r = s.data[i].hcode and msk # "home" location of key@i
shallowCopy(s.data[j], s.data[i]) # data[i] will be marked EMPTY next loop
template dollarImpl() {.dirty.} =
result = "{"
for key in items(s):
if result.len > 1: result.add(", ")
result.addQuoted(key)
result.add("}")
# --------------------------- OrderedSet ------------------------------
proc rawGet[A](t: OrderedSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A],
key: A, hc: Hash, h: Hash) =
rawInsertImpl()
data[h].next = -1
if s.first < 0: s.first = h
if s.last >= 0: data[s.last].next = h
s.last = h
proc enlarge[A](s: var OrderedSet[A]) =
var n: OrderedKeyValuePairSeq[A]
newSeq(n, len(s.data) * growthFactor)
var h = s.first
s.first = -1
s.last = -1
swap(s.data, n)
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode)
rawInsert(s, s.data, n[h].key, n[h].hcode, j)
h = nxt
proc exclImpl[A](s: var OrderedSet[A], key: A) : bool {.inline.} =
if len(s.data) == 0:
return true
var n: OrderedKeyValuePairSeq[A]
newSeq(n, len(s.data))
var h = s.first
s.first = -1
s.last = -1
swap(s.data, n)
let hc = genHash(key)
result = true
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
if n[h].hcode == hc and n[h].key == key:
dec s.counter
result = false
else:
var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode)
rawInsert(s, s.data, n[h].key, n[h].hcode, j)
h = nxt

View file

@ -26,7 +26,7 @@
## `symmetric difference <#symmetricDifference,HashSet[A],HashSet[A]>`_ ## `symmetric difference <#symmetricDifference,HashSet[A],HashSet[A]>`_
## ##
## .. code-block:: ## .. code-block::
## echo toHashSet([9, 5, 1]) # {9, 1, 5} ## echo toHashSet([9, 5, 1]) # {9, 1, 5}
## echo toOrderedSet([9, 5, 1]) # {9, 5, 1} ## echo toOrderedSet([9, 5, 1]) # {9, 5, 1}
## ##
## let ## let
@ -69,148 +69,32 @@ type
data: KeyValuePairSeq[A] data: KeyValuePairSeq[A]
counter: int counter: int
type
OrderedKeyValuePair[A] = tuple[
hcode: Hash, next: int, key: A]
OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]]
OrderedSet* {.myShallow.} [A] = object ## \
## A generic hash set that remembers insertion order.
##
## Use `init proc <#init,OrderedSet[A],int>`_ or `initOrderedSet proc
## <#initOrderedSet,int>`_ before calling other procs on it.
data: OrderedKeyValuePairSeq[A]
counter, first, last: int
# ---------------------- helpers ----------------------------------- const
defaultInitialSize* = 64
const growthFactor = 2 include setimpl
when not defined(nimHasDefault):
template default[T](t: typedesc[T]): T =
## Used by clear methods to get a default value.
var v: T
v
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: Hash = hc and high(s.data) # start with real hash value
while isFilled(s.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A.
if s.data[h].hcode == hc and s.data[h].key == key: # compare hc THEN key
return h
h = nextTry(h, high(s.data))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template genHash(key: typed): Hash =
var hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
hc
template rawGetImpl() {.dirty.} =
hc = genHash(key)
rawGetKnownHCImpl()
template rawInsertImpl() {.dirty.} =
data[h].key = key
data[h].hcode = hc
proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGet[A](s: HashSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A,
hc: Hash, h: Hash) =
rawInsertImpl()
proc enlarge[A](s: var HashSet[A]) =
var n: KeyValuePairSeq[A]
newSeq(n, len(s.data) * growthFactor)
swap(s.data, n) # n is now old seq
for i in countup(0, high(n)):
if isFilled(n[i].hcode):
var j = -1 - rawGetKnownHC(s, n[i].key, n[i].hcode)
rawInsert(s, s.data, n[i].key, n[i].hcode, j)
template inclImpl() {.dirty.} =
var hc: Hash
var index = rawGet(s, key, hc)
if index < 0:
if mustRehash(len(s.data), s.counter):
enlarge(s)
index = rawGetKnownHC(s, key, hc)
rawInsert(s, s.data, key, hc, -1 - index)
inc(s.counter)
template containsOrInclImpl() {.dirty.} =
var hc: Hash
var index = rawGet(s, key, hc)
if index >= 0:
result = true
else:
if mustRehash(len(s.data), s.counter):
enlarge(s)
index = rawGetKnownHC(s, key, hc)
rawInsert(s, s.data, key, hc, -1 - index)
inc(s.counter)
template doWhile(a, b) =
while true:
b
if not a: break
proc exclImpl[A](s: var HashSet[A], key: A) : bool {. inline .} =
assert s.isValid, "The set needs to be initialized."
var hc: Hash
var i = rawGet(s, key, hc)
var msk = high(s.data)
result = true
if i >= 0:
result = false
dec(s.counter)
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
s.data[i].hcode = 0 # mark current EMPTY
s.data[i].key = default(type(s.data[i].key))
doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
i = (i + 1) and msk # increment mod table size
if isEmpty(s.data[i].hcode): # end of collision cluster; So all done
return
r = s.data[i].hcode and msk # "home" location of key@i
shallowCopy(s.data[j], s.data[i]) # data[i] will be marked EMPTY next loop
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
template dollarImpl() {.dirty.} =
result = "{"
for key in items(s):
if result.len > 1: result.add(", ")
result.addQuoted(key)
result.add("}")
proc rightSize*(count: Natural): int {.inline.} proc rightSize*(count: Natural): int {.inline.}
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# ------------------------------ HashSet ------------------------------ # ------------------------------ HashSet ------------------------------
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
proc init*[A](s: var HashSet[A], initialSize=64) = proc init*[A](s: var HashSet[A], initialSize = defaultInitialSize) =
## Initializes a hash set. ## Initializes a hash set.
## ##
## The `initialSize` parameter needs to be a power of two (default: 64). ## The `initialSize` parameter needs to be a power of two (default: 64).
@ -218,9 +102,8 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo>`_ or `rightSize proc ## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo>`_ or `rightSize proc
## <#rightSize,Natural>`_ from this module. ## <#rightSize,Natural>`_ from this module.
## ##
## All set variables must be initialized before ## Starting from Nim v0.20, sets are initialized by default and it is
## use with other procs from this module, with the exception of `isValid proc ## not necessary to call this function explicitly.
## <#isValid,HashSet[A]>`_ and `len() <#len,HashSet[A]>`_.
## ##
## You can call this proc on a previously initialized hash set, which will ## You can call this proc on a previously initialized hash set, which will
## discard all its values. This might be more convenient than iterating over ## discard all its values. This might be more convenient than iterating over
@ -235,42 +118,26 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
init(a) init(a)
assert a.isValid assert a.isValid
assert isPowerOfTwo(initialSize) initImpl(s, initialSize)
s.counter = 0
newSeq(s.data, initialSize)
proc initHashSet*[A](initialSize=64): HashSet[A] = proc initHashSet*[A](initialSize = defaultInitialSize): HashSet[A] =
## Wrapper around `init proc <#init,HashSet[A],int>`_ for initialization of ## Wrapper around `init proc <#init,HashSet[A],int>`_ for initialization of
## hash sets. ## hash sets.
## ##
## Returns an empty hash set you can assign directly in ``var`` blocks in a ## Returns an empty hash set you can assign directly in ``var`` blocks in a
## single line. ## single line.
## ##
## Starting from Nim v0.20, sets are initialized by default and it is
## not necessary to call this function explicitly.
##
## See also: ## See also:
## * `toHashSet proc <#toHashSet,openArray[A]>`_ ## * `toHashSet proc <#toHashSet,openArray[A]>`_
runnableExamples: runnableExamples:
var a = initHashSet[int]() var a = initHashSet[int]()
assert a.isValid
a.incl(3) a.incl(3)
assert len(a) == 1 assert len(a) == 1
result.init(initialSize)
proc isValid*[A](s: HashSet[A]): bool = result.init(initialSize)
## Returns `true` if the set has been initialized (with `initHashSet proc
## <#initHashSet,int>`_ or `init proc <#init,HashSet[A],int>`_).
##
## Most operations over an uninitialized set will crash at runtime and
## `assert <system.html#assert>`_ in debug builds. You can use this proc in
## your own procs to verify that sets passed to your procs are correctly
## initialized.
##
## **Examples:**
##
## .. code-block ::
## proc savePreferences(options: HashSet[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = s.data.len > 0
proc `[]`*[A](s: var HashSet[A], key: A): var A = proc `[]`*[A](s: var HashSet[A], key: A): var A =
## Returns the element that is actually stored in `s` which has the same ## Returns the element that is actually stored in `s` which has the same
@ -278,7 +145,6 @@ proc `[]`*[A](s: var HashSet[A], key: A): var A =
## ##
## This is useful when one overloaded `hash` and `==` but still needs ## This is useful when one overloaded `hash` and `==` but still needs
## reference semantics for sharing. ## reference semantics for sharing.
assert s.isValid, "The set needs to be initialized."
var hc: Hash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
if index >= 0: result = s.data[index].key if index >= 0: result = s.data[index].key
@ -305,7 +171,6 @@ proc contains*[A](s: HashSet[A], key: A): bool =
assert values.contains(2) assert values.contains(2)
assert 2 in values assert 2 in values
assert s.isValid, "The set needs to be initialized."
var hc: Hash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
result = index >= 0 result = index >= 0
@ -325,7 +190,6 @@ proc incl*[A](s: var HashSet[A], key: A) =
values.incl(2) values.incl(2)
assert values.len == 1 assert values.len == 1
assert s.isValid, "The set needs to be initialized."
inclImpl() inclImpl()
proc incl*[A](s: var HashSet[A], other: HashSet[A]) = proc incl*[A](s: var HashSet[A], other: HashSet[A]) =
@ -344,8 +208,6 @@ proc incl*[A](s: var HashSet[A], other: HashSet[A]) =
values.incl(others) values.incl(others)
assert values.len == 5 assert values.len == 5
assert s.isValid, "The set `s` needs to be initialized."
assert other.isValid, "The set `other` needs to be initialized."
for item in other: incl(s, item) for item in other: incl(s, item)
proc toHashSet*[A](keys: openArray[A]): HashSet[A] = proc toHashSet*[A](keys: openArray[A]): HashSet[A] =
@ -368,14 +230,6 @@ proc toHashSet*[A](keys: openArray[A]): HashSet[A] =
result = initHashSet[A](rightSize(keys.len)) result = initHashSet[A](rightSize(keys.len))
for key in items(keys): result.incl(key) for key in items(keys): result.incl(key)
proc initSet*[A](initialSize=64): HashSet[A] {.deprecated:
"Deprecated since v0.20, use `initHashSet`"} = initHashSet[A](initialSize)
## Deprecated since v0.20, use `initHashSet`.
proc toSet*[A](keys: openArray[A]): HashSet[A] {.deprecated:
"Deprecated since v0.20, use `toHashSet`"} = toHashSet[A](keys)
## Deprecated since v0.20, use `toHashSet`.
iterator items*[A](s: HashSet[A]): A = iterator items*[A](s: HashSet[A]): A =
## Iterates over elements of the set `s`. ## Iterates over elements of the set `s`.
## ##
@ -395,8 +249,7 @@ iterator items*[A](s: HashSet[A]): A =
## assert a.len == 2 ## assert a.len == 2
## echo b ## echo b
## # --> {(a: 1, b: 3), (a: 0, b: 4)} ## # --> {(a: 1, b: 3), (a: 0, b: 4)}
assert s.isValid, "The set needs to be initialized." for h in 0 .. high(s.data):
for h in 0..high(s.data):
if isFilled(s.data[h].hcode): yield s.data[h].key if isFilled(s.data[h].hcode): yield s.data[h].key
proc containsOrIncl*[A](s: var HashSet[A], key: A): bool = proc containsOrIncl*[A](s: var HashSet[A], key: A): bool =
@ -417,7 +270,6 @@ proc containsOrIncl*[A](s: var HashSet[A], key: A): bool =
assert values.containsOrIncl(2) == true assert values.containsOrIncl(2) == true
assert values.containsOrIncl(3) == false assert values.containsOrIncl(3) == false
assert s.isValid, "The set needs to be initialized."
containsOrInclImpl() containsOrInclImpl()
proc excl*[A](s: var HashSet[A], key: A) = proc excl*[A](s: var HashSet[A], key: A) =
@ -434,6 +286,7 @@ proc excl*[A](s: var HashSet[A], key: A) =
s.excl(2) s.excl(2)
s.excl(2) s.excl(2)
assert s.len == 3 assert s.len == 3
discard exclImpl(s, key) discard exclImpl(s, key)
proc excl*[A](s: var HashSet[A], other: HashSet[A]) = proc excl*[A](s: var HashSet[A], other: HashSet[A]) =
@ -453,8 +306,6 @@ proc excl*[A](s: var HashSet[A], other: HashSet[A]) =
assert len(numbers) == 3 assert len(numbers) == 3
## numbers == {1, 3, 5} ## numbers == {1, 3, 5}
assert s.isValid, "The set `s` needs to be initialized."
assert other.isValid, "The set `other` needs to be initialized."
for item in other: discard exclImpl(s, item) for item in other: discard exclImpl(s, item)
proc missingOrExcl*[A](s: var HashSet[A], key: A): bool = proc missingOrExcl*[A](s: var HashSet[A], key: A): bool =
@ -474,6 +325,7 @@ proc missingOrExcl*[A](s: var HashSet[A], key: A): bool =
assert s.missingOrExcl(4) == true assert s.missingOrExcl(4) == true
assert s.missingOrExcl(6) == false assert s.missingOrExcl(6) == false
assert s.missingOrExcl(6) == true assert s.missingOrExcl(6) == true
exclImpl(s, key) exclImpl(s, key)
proc pop*[A](s: var HashSet[A]): A = proc pop*[A](s: var HashSet[A]): A =
@ -489,7 +341,7 @@ proc pop*[A](s: var HashSet[A]): A =
assert s.pop == 2 assert s.pop == 2
doAssertRaises(KeyError, echo s.pop) doAssertRaises(KeyError, echo s.pop)
for h in 0..high(s.data): for h in 0 .. high(s.data):
if isFilled(s.data[h].hcode): if isFilled(s.data[h].hcode):
result = s.data[h].key result = s.data[h].key
excl(s, result) excl(s, result)
@ -510,7 +362,7 @@ proc clear*[A](s: var HashSet[A]) =
assert len(s) == 0 assert len(s) == 0
s.counter = 0 s.counter = 0
for i in 0..<s.data.len: for i in 0 ..< s.data.len:
s.data[i].hcode = 0 s.data[i].hcode = 0
s.data[i].key = default(type(s.data[i].key)) s.data[i].key = default(type(s.data[i].key))
@ -525,6 +377,7 @@ proc len*[A](s: HashSet[A]): int =
assert len(a) == 0 assert len(a) == 0
let s = toHashSet([3, 5, 7]) let s = toHashSet([3, 5, 7])
assert len(s) == 3 assert len(s) == 3
result = s.counter result = s.counter
proc card*[A](s: HashSet[A]): int = proc card*[A](s: HashSet[A]): int =
@ -554,8 +407,6 @@ proc union*[A](s1, s2: HashSet[A]): HashSet[A] =
c = union(a, b) c = union(a, b)
assert c == toHashSet(["a", "b", "c"]) assert c == toHashSet(["a", "b", "c"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = s1 result = s1
incl(result, s2) incl(result, s2)
@ -579,9 +430,7 @@ proc intersection*[A](s1, s2: HashSet[A]): HashSet[A] =
c = intersection(a, b) c = intersection(a, b)
assert c == toHashSet(["b"]) assert c == toHashSet(["b"])
assert s1.isValid, "The set `s1` needs to be initialized." result = initHashSet[A](max(min(s1.data.len, s2.data.len), 2))
assert s2.isValid, "The set `s2` needs to be initialized."
result = initHashSet[A](min(s1.data.len, s2.data.len))
for item in s1: for item in s1:
if item in s2: incl(result, item) if item in s2: incl(result, item)
@ -604,8 +453,6 @@ proc difference*[A](s1, s2: HashSet[A]): HashSet[A] =
c = difference(a, b) c = difference(a, b)
assert c == toHashSet(["a"]) assert c == toHashSet(["a"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = initHashSet[A]() result = initHashSet[A]()
for item in s1: for item in s1:
if not contains(s2, item): if not contains(s2, item):
@ -631,8 +478,6 @@ proc symmetricDifference*[A](s1, s2: HashSet[A]): HashSet[A] =
c = symmetricDifference(a, b) c = symmetricDifference(a, b)
assert c == toHashSet(["a", "c"]) assert c == toHashSet(["a", "c"])
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
result = s1 result = s1
for item in s2: for item in s2:
if containsOrIncl(result, item): excl(result, item) if containsOrIncl(result, item): excl(result, item)
@ -663,8 +508,6 @@ proc disjoint*[A](s1, s2: HashSet[A]): bool =
assert disjoint(a, b) == false assert disjoint(a, b) == false
assert disjoint(a, b - a) == true assert disjoint(a, b - a) == true
assert s1.isValid, "The set `s1` needs to be initialized."
assert s2.isValid, "The set `s2` needs to be initialized."
for item in s1: for item in s1:
if item in s2: return false if item in s2: return false
return true return true
@ -681,6 +524,7 @@ proc `<`*[A](s, t: HashSet[A]): bool =
c = intersection(a, b) c = intersection(a, b)
assert c < a and c < b assert c < a and c < b
assert(not (a < a)) assert(not (a < a))
s.counter != t.counter and s <= t s.counter != t.counter and s <= t
proc `<=`*[A](s, t: HashSet[A]): bool = proc `<=`*[A](s, t: HashSet[A]): bool =
@ -711,6 +555,7 @@ proc `==`*[A](s, t: HashSet[A]): bool =
a = toHashSet([1, 2]) a = toHashSet([1, 2])
b = toHashSet([2, 1]) b = toHashSet([2, 1])
assert a == b assert a == b
s.counter == t.counter and s <= t s.counter == t.counter and s <= t
proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] = proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] =
@ -729,8 +574,7 @@ proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] =
proc hash*[A](s: HashSet[A]): Hash = proc hash*[A](s: HashSet[A]): Hash =
## Hashing of HashSet. ## Hashing of HashSet.
assert s.isValid, "The set needs to be initialized." for h in 0 .. high(s.data):
for h in 0..high(s.data):
result = result xor s.data[h].hcode result = result xor s.data[h].hcode
result = !$result result = !$result
@ -747,7 +591,6 @@ proc `$`*[A](s: HashSet[A]): string =
## # --> {2, 4, 5} ## # --> {2, 4, 5}
## echo toHashSet(["no", "esc'aping", "is \" provided"]) ## echo toHashSet(["no", "esc'aping", "is \" provided"])
## # --> {no, esc'aping, is " provided} ## # --> {no, esc'aping, is " provided}
assert s.isValid, "The set needs to be initialized."
dollarImpl() dollarImpl()
proc rightSize*(count: Natural): int {.inline.} = proc rightSize*(count: Natural): int {.inline.} =
@ -760,8 +603,28 @@ proc rightSize*(count: Natural): int {.inline.} =
result = nextPowerOfTwo(count * 3 div 2 + 4) result = nextPowerOfTwo(count * 3 div 2 + 4)
proc initSet*[A](initialSize = defaultInitialSize): HashSet[A] {.deprecated:
"Deprecated since v0.20, use `initHashSet`"} = initHashSet[A](initialSize)
## Deprecated since v0.20, use `initHashSet <#initHashSet,int>`_.
proc toSet*[A](keys: openArray[A]): HashSet[A] {.deprecated:
"Deprecated since v0.20, use `toHashSet`"} = toHashSet[A](keys)
## Deprecated since v0.20, use `toHashSet <#toHashSet,openArray[A]>`_.
proc isValid*[A](s: HashSet[A]): bool {.deprecated:
"Deprecated since v0.20; sets are initialized by default"} =
## **Deprecated since v0.20; sets are initialized by default**
##
## Returns `true` if the set has been initialized (with `initHashSet proc
## <#initHashSet,int>`_ or `init proc <#init,HashSet[A],int>`_).
##
## **Examples:**
##
## .. code-block ::
## proc savePreferences(options: HashSet[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = s.data.len > 0
@ -769,89 +632,19 @@ proc rightSize*(count: Natural): int {.inline.} =
# --------------------------- OrderedSet ------------------------------ # --------------------------- OrderedSet ------------------------------
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
type
OrderedKeyValuePair[A] = tuple[
hcode: Hash, next: int, key: A]
OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]]
OrderedSet* {.myShallow.} [A] = object ## \
## A generic hash set that remembers insertion order.
##
## Use `init proc <#init,OrderedSet[A],int>`_ or `initOrderedSet proc
## <#initOrderedSet,int>`_ before calling other procs on it.
data: OrderedKeyValuePairSeq[A]
counter, first, last: int
# ---------------------- helpers -----------------------------------
template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} =
var h = s.first if s.data.len > 0:
var idx = 0 var h = s.first
while h >= 0: var idx = 0
var nxt = s.data[h].next while h >= 0:
if isFilled(s.data[h].hcode): var nxt = s.data[h].next
yieldStmt if isFilled(s.data[h].hcode):
inc(idx) yieldStmt
h = nxt inc(idx)
h = nxt
proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGet[A](s: OrderedSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A],
key: A, hc: Hash, h: Hash) =
rawInsertImpl()
data[h].next = -1
if s.first < 0: s.first = h
if s.last >= 0: data[s.last].next = h
s.last = h
proc enlarge[A](s: var OrderedSet[A]) =
var n: OrderedKeyValuePairSeq[A]
newSeq(n, len(s.data) * growthFactor)
var h = s.first
s.first = -1
s.last = -1
swap(s.data, n)
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode)
rawInsert(s, s.data, n[h].key, n[h].hcode, j)
h = nxt
proc isValid*[A](s: OrderedSet[A]): bool
proc exclImpl[A](s: var OrderedSet[A], key: A) : bool {. inline .} =
assert s.isValid, "The set needs to be initialized."
var n: OrderedKeyValuePairSeq[A]
newSeq(n, len(s.data))
var h = s.first
s.first = -1
s.last = -1
swap(s.data, n)
let hc = genHash(key)
result = true
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
if n[h].hcode == hc and n[h].key == key:
dec s.counter
result = false
else:
var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode)
rawInsert(s, s.data, n[h].key, n[h].hcode, j)
h = nxt
proc init*[A](s: var OrderedSet[A], initialSize = defaultInitialSize) =
# -----------------------------------------------------------------------
proc init*[A](s: var OrderedSet[A], initialSize=64) =
## Initializes an ordered hash set. ## Initializes an ordered hash set.
## ##
## The `initialSize` parameter needs to be a power of two (default: 64). ## The `initialSize` parameter needs to be a power of two (default: 64).
@ -859,9 +652,8 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) =
## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo>`_ or `rightSize proc ## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo>`_ or `rightSize proc
## <#rightSize,Natural>`_ from this module. ## <#rightSize,Natural>`_ from this module.
## ##
## All set variables must be initialized before ## Starting from Nim v0.20, sets are initialized by default and it is
## use with other procs from this module, with the exception of `isValid proc ## not necessary to call this function explicitly.
## <#isValid,HashSet[A]>`_ and `len() <#len,HashSet[A]>`_.
## ##
## You can call this proc on a previously initialized hash set, which will ## You can call this proc on a previously initialized hash set, which will
## discard all its values. This might be more convenient than iterating over ## discard all its values. This might be more convenient than iterating over
@ -876,26 +668,25 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) =
init(a) init(a)
assert a.isValid assert a.isValid
assert isPowerOfTwo(initialSize) initImpl(s, initialSize)
s.counter = 0
s.first = -1
s.last = -1
newSeq(s.data, initialSize)
proc initOrderedSet*[A](initialSize=64): OrderedSet[A] = proc initOrderedSet*[A](initialSize = defaultInitialSize): OrderedSet[A] =
## Wrapper around `init proc <#init,OrderedSet[A],int>`_ for initialization of ## Wrapper around `init proc <#init,OrderedSet[A],int>`_ for initialization of
## ordered hash sets. ## ordered hash sets.
## ##
## Returns an empty ordered hash set you can assign directly in ``var`` blocks ## Returns an empty ordered hash set you can assign directly in ``var`` blocks
## in a single line. ## in a single line.
## ##
## Starting from Nim v0.20, sets are initialized by default and it is
## not necessary to call this function explicitly.
##
## See also: ## See also:
## * `toOrderedSet proc <#toOrderedSet,openArray[A]>`_ ## * `toOrderedSet proc <#toOrderedSet,openArray[A]>`_
runnableExamples: runnableExamples:
var a = initOrderedSet[int]() var a = initOrderedSet[int]()
assert a.isValid
a.incl(3) a.incl(3)
assert len(a) == 1 assert len(a) == 1
result.init(initialSize) result.init(initialSize)
proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] = proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] =
@ -918,23 +709,6 @@ proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] =
result = initOrderedSet[A](rightSize(keys.len)) result = initOrderedSet[A](rightSize(keys.len))
for key in items(keys): result.incl(key) for key in items(keys): result.incl(key)
proc isValid*[A](s: OrderedSet[A]): bool =
## Returns `true` if the set has been initialized (with `initHashSet proc
## <#initOrderedSet,int>`_ or `init proc <#init,OrderedSet[A],int>`_).
##
## Most operations over an uninitialized set will crash at runtime and
## `assert <system.html#assert>`_ in debug builds. You can use this proc in
## your own procs to verify that sets passed to your procs are correctly
## initialized.
##
## **Examples:**
##
## .. code-block ::
## proc savePreferences(options: OrderedSet[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = s.data.len > 0
proc contains*[A](s: OrderedSet[A], key: A): bool = proc contains*[A](s: OrderedSet[A], key: A): bool =
## Returns true if `key` is in `s`. ## Returns true if `key` is in `s`.
## ##
@ -952,7 +726,6 @@ proc contains*[A](s: OrderedSet[A], key: A): bool =
assert values.contains(2) assert values.contains(2)
assert 2 in values assert 2 in values
assert s.isValid, "The set needs to be initialized."
var hc: Hash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
result = index >= 0 result = index >= 0
@ -972,7 +745,6 @@ proc incl*[A](s: var OrderedSet[A], key: A) =
values.incl(2) values.incl(2)
assert values.len == 1 assert values.len == 1
assert s.isValid, "The set needs to be initialized."
inclImpl() inclImpl()
proc incl*[A](s: var HashSet[A], other: OrderedSet[A]) = proc incl*[A](s: var HashSet[A], other: OrderedSet[A]) =
@ -988,8 +760,7 @@ proc incl*[A](s: var HashSet[A], other: OrderedSet[A]) =
others = toOrderedSet([3, 4, 5]) others = toOrderedSet([3, 4, 5])
values.incl(others) values.incl(others)
assert values.len == 5 assert values.len == 5
assert s.isValid, "The set `s` needs to be initialized."
assert other.isValid, "The set `other` needs to be initialized."
for item in items(other): incl(s, item) for item in items(other): incl(s, item)
proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool = proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool =
@ -1009,7 +780,6 @@ proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool =
assert values.containsOrIncl(2) == true assert values.containsOrIncl(2) == true
assert values.containsOrIncl(3) == false assert values.containsOrIncl(3) == false
assert s.isValid, "The set needs to be initialized."
containsOrInclImpl() containsOrInclImpl()
proc excl*[A](s: var OrderedSet[A], key: A) = proc excl*[A](s: var OrderedSet[A], key: A) =
@ -1025,6 +795,7 @@ proc excl*[A](s: var OrderedSet[A], key: A) =
s.excl(2) s.excl(2)
s.excl(2) s.excl(2)
assert s.len == 3 assert s.len == 3
discard exclImpl(s, key) discard exclImpl(s, key)
proc missingOrExcl*[A](s: var OrderedSet[A], key: A): bool = proc missingOrExcl*[A](s: var OrderedSet[A], key: A): bool =
@ -1044,6 +815,7 @@ proc missingOrExcl*[A](s: var OrderedSet[A], key: A): bool =
assert s.missingOrExcl(4) == true assert s.missingOrExcl(4) == true
assert s.missingOrExcl(6) == false assert s.missingOrExcl(6) == false
assert s.missingOrExcl(6) == true assert s.missingOrExcl(6) == true
exclImpl(s, key) exclImpl(s, key)
proc clear*[A](s: var OrderedSet[A]) = proc clear*[A](s: var OrderedSet[A]) =
@ -1059,7 +831,7 @@ proc clear*[A](s: var OrderedSet[A]) =
s.counter = 0 s.counter = 0
s.first = -1 s.first = -1
s.last = -1 s.last = -1
for i in 0..<s.data.len: for i in 0 ..< s.data.len:
s.data[i].hcode = 0 s.data[i].hcode = 0
s.data[i].next = 0 s.data[i].next = 0
s.data[i].key = default(type(s.data[i].key)) s.data[i].key = default(type(s.data[i].key))
@ -1075,6 +847,7 @@ proc len*[A](s: OrderedSet[A]): int {.inline.} =
assert len(a) == 0 assert len(a) == 0
let s = toHashSet([3, 5, 7]) let s = toHashSet([3, 5, 7])
assert len(s) == 3 assert len(s) == 3
result = s.counter result = s.counter
proc card*[A](s: OrderedSet[A]): int {.inline.} = proc card*[A](s: OrderedSet[A]): int {.inline.} =
@ -1110,7 +883,6 @@ proc `==`*[A](s, t: OrderedSet[A]): bool =
proc hash*[A](s: OrderedSet[A]): Hash = proc hash*[A](s: OrderedSet[A]): Hash =
## Hashing of OrderedSet. ## Hashing of OrderedSet.
assert s.isValid, "The set needs to be initialized."
forAllOrderedPairs: forAllOrderedPairs:
result = result !& s.data[h].hcode result = result !& s.data[h].hcode
result = !$result result = !$result
@ -1129,7 +901,6 @@ proc `$`*[A](s: OrderedSet[A]): string =
## # --> {2, 4, 5} ## # --> {2, 4, 5}
## echo toOrderedSet(["no", "esc'aping", "is \" provided"]) ## echo toOrderedSet(["no", "esc'aping", "is \" provided"])
## # --> {no, esc'aping, is " provided} ## # --> {no, esc'aping, is " provided}
assert s.isValid, "The set needs to be initialized."
dollarImpl() dollarImpl()
@ -1152,11 +923,9 @@ iterator items*[A](s: OrderedSet[A]): A =
## # --> Got 5 ## # --> Got 5
## # --> Got 8 ## # --> Got 8
## # --> Got 4 ## # --> Got 4
assert s.isValid, "The set needs to be initialized."
forAllOrderedPairs: forAllOrderedPairs:
yield s.data[h].key yield s.data[h].key
iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] =
## Iterates through (position, value) tuples of OrderedSet `s`. ## Iterates through (position, value) tuples of OrderedSet `s`.
runnableExamples: runnableExamples:
@ -1166,12 +935,27 @@ iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] =
p.add(x) p.add(x)
assert p == @[(0, 'a'), (1, 'b'), (2, 'r'), (3, 'c'), (4, 'd')] assert p == @[(0, 'a'), (1, 'b'), (2, 'r'), (3, 'c'), (4, 'd')]
assert s.isValid, "The set needs to be initialized"
forAllOrderedPairs: forAllOrderedPairs:
yield (idx, s.data[h].key) yield (idx, s.data[h].key)
proc isValid*[A](s: OrderedSet[A]): bool {.deprecated:
"Deprecated since v0.20; sets are initialized by default"} =
## **Deprecated since v0.20; sets are initialized by default**
##
## Returns `true` if the set has been initialized (with `initHashSet proc
## <#initOrderedSet,int>`_ or `init proc <#init,OrderedSet[A],int>`_).
##
## **Examples:**
##
## .. code-block ::
## proc savePreferences(options: OrderedSet[string]) =
## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds!
result = s.data.len > 0
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -1179,7 +963,7 @@ iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] =
when isMainModule and not defined(release): when isMainModule and not defined(release):
proc testModule() = proc testModule() =
## Internal micro test to validate docstrings and such. ## Internal micro test to validate docstrings and such.
block isValidTest: block isValidTest: # isValid is deprecated
var options: HashSet[string] var options: HashSet[string]
proc savePreferences(options: HashSet[string]) = proc savePreferences(options: HashSet[string]) =
assert options.isValid, "Pass an initialized set!" assert options.isValid, "Pass an initialized set!"
@ -1280,7 +1064,7 @@ when isMainModule and not defined(release):
var b = a.map(proc (x: int): string = $x) var b = a.map(proc (x: int): string = $x)
assert b == toHashSet(["1", "2", "3"]) assert b == toHashSet(["1", "2", "3"])
block isValidTest: block isValidTest: # isValid is deprecated
var cards: OrderedSet[string] var cards: OrderedSet[string]
proc saveTarotCards(cards: OrderedSet[string]) = proc saveTarotCards(cards: OrderedSet[string]) =
assert cards.isValid, "Pass an initialized set!" assert cards.isValid, "Pass an initialized set!"
@ -1393,6 +1177,69 @@ when isMainModule and not defined(release):
bb.incl(y) bb.incl(y)
assert aa == bb assert aa == bb
block setsWithoutInit:
var
a: HashSet[int]
b: HashSet[int]
c: HashSet[int]
d: HashSet[int]
e: HashSet[int]
doAssert a.containsOrIncl(3) == false
doAssert a.contains(3)
doAssert a.len == 1
doAssert a.containsOrIncl(3)
a.incl(3)
doAssert a.len == 1
a.incl(6)
doAssert a.len == 2
b.incl(5)
doAssert b.len == 1
b.excl(5)
b.excl(c)
doAssert b.missingOrExcl(5)
doAssert b.disjoint(c)
d = b + c
doAssert d.len == 0
d = b * c
doAssert d.len == 0
d = b - c
doAssert d.len == 0
d = b -+- c
doAssert d.len == 0
doAssert (d < e) == false
doAssert d <= e
doAssert d == e
block setsWithoutInit:
var
a: OrderedSet[int]
b: OrderedSet[int]
c: OrderedSet[int]
d: HashSet[int]
doAssert a.containsOrIncl(3) == false
doAssert a.contains(3)
doAssert a.len == 1
doAssert a.containsOrIncl(3)
a.incl(3)
doAssert a.len == 1
a.incl(6)
doAssert a.len == 2
b.incl(5)
doAssert b.len == 1
doAssert b.missingOrExcl(5) == false
doAssert b.missingOrExcl(5)
doAssert c.missingOrExcl(9)
d.incl(c)
doAssert d.len == 0
when not defined(testing): when not defined(testing):
echo "Micro tests run successfully." echo "Micro tests run successfully."

View file

@ -29,6 +29,14 @@ template maxHash(t): untyped = t.dataLen-1
include tableimpl include tableimpl
template st_maybeRehashPutImpl(enlarge) {.dirty.} =
if mustRehash(t.dataLen, t.counter):
enlarge(t)
index = rawGetKnownHC(t, key, hc)
index = -1 - index # important to transform for mgetOrPutImpl
rawInsert(t, t.data, key, val, hc, index)
inc(t.counter)
proc enlarge[A, B](t: var SharedTable[A, B]) = proc enlarge[A, B](t: var SharedTable[A, B]) =
let oldSize = t.dataLen let oldSize = t.dataLen
let size = oldSize * growthFactor let size = oldSize * growthFactor
@ -176,7 +184,7 @@ proc withKey*[A, B](t: var SharedTable[A, B], key: A,
var val: B var val: B
mapper(key, val, pairExists) mapper(key, val, pairExists)
if pairExists: if pairExists:
maybeRehashPutImpl(enlarge) st_maybeRehashPutImpl(enlarge)
proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) = proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`. ## puts a (key, value)-pair into `t`.

View file

@ -9,49 +9,7 @@
# An ``include`` file for the different table implementations. # An ``include`` file for the different table implementations.
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These include hashcommon
# two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0
const
growthFactor = 2
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: Hash = hc and maxHash(t) # start with real hash value
while isFilled(t.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A.
if t.data[h].hcode == hc and t.data[h].key == key:
return h
h = nextTry(h, maxHash(t))
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template genHashImpl(key, hc: typed) =
hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
template genHash(key: typed): Hash =
var res: Hash
genHashImpl(key, res)
res
template rawGetImpl() {.dirty.} =
genHashImpl(key, hc)
rawGetKnownHCImpl()
template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add
genHashImpl(key, hc) genHashImpl(key, hc)
@ -65,20 +23,16 @@ template rawInsertImpl() {.dirty.} =
data[h].val = val data[h].val = val
data[h].hcode = hc data[h].hcode = hc
proc rawGetKnownHC[X, A](t: X, key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGetDeep[X, A](t: X, key: A, hc: var Hash): int {.inline.} = proc rawGetDeep[X, A](t: X, key: A, hc: var Hash): int {.inline.} =
rawGetDeepImpl() rawGetDeepImpl()
proc rawGet[X, A](t: X, key: A, hc: var Hash): int {.inline.} =
rawGetImpl()
proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B], proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B],
key: A, val: B, hc: Hash, h: Hash) = key: A, val: B, hc: Hash, h: Hash) =
rawInsertImpl() rawInsertImpl()
template addImpl(enlarge) {.dirty.} = template addImpl(enlarge) {.dirty.} =
if t.dataLen == 0:
initImpl(t, defaultInitialSize)
if mustRehash(t.dataLen, t.counter): enlarge(t) if mustRehash(t.dataLen, t.counter): enlarge(t)
var hc: Hash var hc: Hash
var j = rawGetDeep(t, key, hc) var j = rawGetDeep(t, key, hc)
@ -86,6 +40,8 @@ template addImpl(enlarge) {.dirty.} =
inc(t.counter) inc(t.counter)
template maybeRehashPutImpl(enlarge) {.dirty.} = template maybeRehashPutImpl(enlarge) {.dirty.} =
if t.dataLen == 0:
initImpl(t, defaultInitialSize)
if mustRehash(t.dataLen, t.counter): if mustRehash(t.dataLen, t.counter):
enlarge(t) enlarge(t)
index = rawGetKnownHC(t, key, hc) index = rawGetKnownHC(t, key, hc)
@ -94,12 +50,16 @@ template maybeRehashPutImpl(enlarge) {.dirty.} =
inc(t.counter) inc(t.counter)
template putImpl(enlarge) {.dirty.} = template putImpl(enlarge) {.dirty.} =
if t.dataLen == 0:
initImpl(t, defaultInitialSize)
var hc: Hash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index >= 0: t.data[index].val = val if index >= 0: t.data[index].val = val
else: maybeRehashPutImpl(enlarge) else: maybeRehashPutImpl(enlarge)
template mgetOrPutImpl(enlarge) {.dirty.} = template mgetOrPutImpl(enlarge) {.dirty.} =
if t.dataLen == 0:
initImpl(t, defaultInitialSize)
var hc: Hash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index < 0: if index < 0:
@ -109,6 +69,8 @@ template mgetOrPutImpl(enlarge) {.dirty.} =
result = t.data[index].val result = t.data[index].val
template hasKeyOrPutImpl(enlarge) {.dirty.} = template hasKeyOrPutImpl(enlarge) {.dirty.} =
if t.dataLen == 0:
initImpl(t, defaultInitialSize)
var hc: Hash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index < 0: if index < 0:
@ -116,11 +78,6 @@ template hasKeyOrPutImpl(enlarge) {.dirty.} =
maybeRehashPutImpl(enlarge) maybeRehashPutImpl(enlarge)
else: result = true else: result = true
when not defined(nimHasDefault):
template default[T](t: typedesc[T]): T =
var v: T
v
template delImplIdx(t, i) = template delImplIdx(t, i) =
let msk = maxHash(t) let msk = maxHash(t)
if i >= 0: if i >= 0:
@ -150,9 +107,53 @@ template delImpl() {.dirty.} =
delImplIdx(t, i) delImplIdx(t, i)
template clearImpl() {.dirty.} = template clearImpl() {.dirty.} =
for i in 0 ..< t.data.len: for i in 0 ..< t.dataLen:
when compiles(t.data[i].hcode): # CountTable records don't contain a hcode when compiles(t.data[i].hcode): # CountTable records don't contain a hcode
t.data[i].hcode = 0 t.data[i].hcode = 0
t.data[i].key = default(type(t.data[i].key)) t.data[i].key = default(type(t.data[i].key))
t.data[i].val = default(type(t.data[i].val)) t.data[i].val = default(type(t.data[i].val))
t.counter = 0 t.counter = 0
template initImpl(result: typed, size: int) =
assert isPowerOfTwo(size)
result.counter = 0
newSeq(result.data, size)
template insertImpl() = # for CountTable
if t.dataLen == 0: initImpl(t, defaultInitialSize)
if mustRehash(len(t.data), t.counter): enlarge(t)
ctRawInsert(t, t.data, key, val)
inc(t.counter)
template getOrDefaultImpl(t, key): untyped =
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
template getOrDefaultImpl(t, key, default: untyped): untyped =
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
result = if index >= 0: t.data[index].val else: default
template dollarImpl(): untyped {.dirty.} =
if t.len == 0:
result = "{:}"
else:
result = "{"
for key, val in pairs(t):
if result.len > 1: result.add(", ")
result.addQuoted(key)
result.add(": ")
result.addQuoted(val)
result.add("}")
template equalsImpl(s, t: typed): typed =
if s.counter == t.counter:
# different insertion orders mean different 'data' seqs, so we have
# to use the slow route here:
for key, val in s:
if not t.hasKey(key): return false
if t.getOrDefault(key) != val: return false
return true

View file

@ -237,9 +237,13 @@ type
## For creating a new empty TableRef, use `newTable proc ## For creating a new empty TableRef, use `newTable proc
## <#newTable,int>`_. ## <#newTable,int>`_.
const
defaultInitialSize* = 64
# ------------------------------ helpers --------------------------------- # ------------------------------ helpers ---------------------------------
# Do NOT move these to tableimpl.nim, because sharedtables uses that
# file and has its own implementation.
template maxHash(t): untyped = high(t.data) template maxHash(t): untyped = high(t.data)
template dataLen(t): untyped = len(t.data) template dataLen(t): untyped = len(t.data)
@ -260,30 +264,6 @@ template get(t, key): untyped =
else: else:
raise newException(KeyError, "key not found") raise newException(KeyError, "key not found")
template getOrDefaultImpl(t, key): untyped =
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
template getOrDefaultImpl(t, key, default: untyped): untyped =
mixin rawGet
var hc: Hash
var index = rawGet(t, key, hc)
result = if index >= 0: t.data[index].val else: default
template dollarImpl(): untyped {.dirty.} =
if t.len == 0:
result = "{:}"
else:
result = "{"
for key, val in pairs(t):
if result.len > 1: result.add(", ")
result.addQuoted(key)
result.add(": ")
result.addQuoted(val)
result.add("}")
proc enlarge[A, B](t: var Table[A, B]) = proc enlarge[A, B](t: var Table[A, B]) =
var n: KeyValuePairSeq[A, B] var n: KeyValuePairSeq[A, B]
newSeq(n, len(t.data) * growthFactor) newSeq(n, len(t.data) * growthFactor)
@ -296,14 +276,6 @@ proc enlarge[A, B](t: var Table[A, B]) =
j = nextTry(j, maxHash(t)) j = nextTry(j, maxHash(t))
rawInsert(t, t.data, n[i].key, n[i].val, eh, j) rawInsert(t, t.data, n[i].key, n[i].val, eh, j)
template equalsImpl(s, t: typed): typed =
if s.counter == t.counter:
# different insertion orders mean different 'data' seqs, so we have
# to use the slow route here:
for key, val in s:
if not t.hasKey(key): return false
if t.getOrDefault(key) != val: return false
return true
@ -311,7 +283,7 @@ template equalsImpl(s, t: typed): typed =
# ------------------------------ Table ------------------------------ # ------------------------------ Table ------------------------------
# ------------------------------------------------------------------- # -------------------------------------------------------------------
proc initTable*[A, B](initialSize=64): Table[A, B] = proc initTable*[A, B](initialsize = defaultInitialSize): Table[A, B] =
## Creates a new hash table that is empty. ## Creates a new hash table that is empty.
## ##
## ``initialSize`` must be a power of two (default: 64). ## ``initialSize`` must be a power of two (default: 64).
@ -320,6 +292,9 @@ proc initTable*[A, B](initialSize=64): Table[A, B] =
## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_
## from this module. ## from this module.
## ##
## Starting from Nim v0.20, tables are initialized by default and it is
## not necessary to call this function explicitly.
##
## See also: ## See also:
## * `toTable proc<#toTable,openArray[]>`_ ## * `toTable proc<#toTable,openArray[]>`_
## * `newTable proc<#newTable,int>`_ for creating a `TableRef` ## * `newTable proc<#newTable,int>`_ for creating a `TableRef`
@ -327,9 +302,7 @@ proc initTable*[A, B](initialSize=64): Table[A, B] =
let let
a = initTable[int, string]() a = initTable[int, string]()
b = initTable[char, seq[int]]() b = initTable[char, seq[int]]()
assert isPowerOfTwo(initialSize) initImpl(result, initialSize)
result.counter = 0
newSeq(result.data, initialSize)
proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] =
## Creates a new hash table that contains the given ``pairs``. ## Creates a new hash table that contains the given ``pairs``.
@ -343,6 +316,7 @@ proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] =
let a = [('a', 5), ('b', 9)] let a = [('a', 5), ('b', 9)]
let b = toTable(a) let b = toTable(a)
assert b == {'a': 5, 'b': 9}.toTable assert b == {'a': 5, 'b': 9}.toTable
result = initTable[A, B](rightSize(pairs.len)) result = initTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result[key] = val for key, val in items(pairs): result[key] = val
@ -398,6 +372,7 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) =
a['x'] = 7 a['x'] = 7
a['y'] = 33 a['y'] = 33
doAssert a == {'x': 7, 'y': 33}.toTable doAssert a == {'x': 7, 'y': 33}.toTable
putImpl(enlarge) putImpl(enlarge)
proc hasKey*[A, B](t: Table[A, B], key: A): bool = proc hasKey*[A, B](t: Table[A, B], key: A): bool =
@ -414,6 +389,7 @@ proc hasKey*[A, B](t: Table[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.toTable let a = {'a': 5, 'b': 9}.toTable
doAssert a.hasKey('a') == true doAssert a.hasKey('a') == true
doAssert a.hasKey('z') == false doAssert a.hasKey('z') == false
var hc: Hash var hc: Hash
result = rawGet(t, key, hc) >= 0 result = rawGet(t, key, hc) >= 0
@ -424,6 +400,7 @@ proc contains*[A, B](t: Table[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.toTable let a = {'a': 5, 'b': 9}.toTable
doAssert 'b' in a == true doAssert 'b' in a == true
doAssert a.contains('z') == false doAssert a.contains('z') == false
return hasKey[A, B](t, key) return hasKey[A, B](t, key)
proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool = proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool =
@ -443,6 +420,7 @@ proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool =
if a.hasKeyOrPut('z', 50): if a.hasKeyOrPut('z', 50):
a['z'] = 99 a['z'] = 99
doAssert a == {'a': 99, 'b': 9, 'z': 50}.toTable doAssert a == {'a': 99, 'b': 9, 'z': 50}.toTable
hasKeyOrPutImpl(enlarge) hasKeyOrPutImpl(enlarge)
proc getOrDefault*[A, B](t: Table[A, B], key: A): B = proc getOrDefault*[A, B](t: Table[A, B], key: A): B =
@ -461,6 +439,7 @@ proc getOrDefault*[A, B](t: Table[A, B], key: A): B =
let a = {'a': 5, 'b': 9}.toTable let a = {'a': 5, 'b': 9}.toTable
doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('a') == 5
doAssert a.getOrDefault('z') == 0 doAssert a.getOrDefault('z') == 0
getOrDefaultImpl(t, key) getOrDefaultImpl(t, key)
proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B = proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B =
@ -478,6 +457,7 @@ proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B =
let a = {'a': 5, 'b': 9}.toTable let a = {'a': 5, 'b': 9}.toTable
doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('a', 99) == 5
doAssert a.getOrDefault('z', 99) == 99 doAssert a.getOrDefault('z', 99) == 99
getOrDefaultImpl(t, key, default) getOrDefaultImpl(t, key, default)
proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B = proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B =
@ -497,6 +477,7 @@ proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B =
doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('a', 99) == 5
doAssert a.mgetOrPut('z', 99) == 99 doAssert a.mgetOrPut('z', 99) == 99
doAssert a == {'a': 5, 'b': 9, 'z': 99}.toTable doAssert a == {'a': 5, 'b': 9, 'z': 99}.toTable
mgetOrPutImpl(enlarge) mgetOrPutImpl(enlarge)
proc len*[A, B](t: Table[A, B]): int = proc len*[A, B](t: Table[A, B]): int =
@ -504,6 +485,7 @@ proc len*[A, B](t: Table[A, B]): int =
runnableExamples: runnableExamples:
let a = {'a': 5, 'b': 9}.toTable let a = {'a': 5, 'b': 9}.toTable
doAssert len(a) == 2 doAssert len(a) == 2
result = t.counter result = t.counter
proc add*[A, B](t: var Table[A, B], key: A, val: B) = proc add*[A, B](t: var Table[A, B], key: A, val: B) =
@ -527,6 +509,7 @@ proc del*[A, B](t: var Table[A, B], key: A) =
doAssert a == {'b': 9, 'c': 13}.toTable doAssert a == {'b': 9, 'c': 13}.toTable
a.del('z') a.del('z')
doAssert a == {'b': 9, 'c': 13}.toTable doAssert a == {'b': 9, 'c': 13}.toTable
delImpl() delImpl()
proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool = proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool =
@ -568,6 +551,7 @@ proc clear*[A, B](t: var Table[A, B]) =
doAssert len(a) == 3 doAssert len(a) == 3
clear(a) clear(a)
doAssert len(a) == 0 doAssert len(a) == 0
clearImpl() clearImpl()
proc `$`*[A, B](t: Table[A, B]): string = proc `$`*[A, B](t: Table[A, B]): string =
@ -583,6 +567,7 @@ proc `==`*[A, B](s, t: Table[A, B]): bool =
a = {'a': 5, 'b': 9, 'c': 13}.toTable a = {'a': 5, 'b': 9, 'c': 13}.toTable
b = {'b': 9, 'c': 13, 'a': 5}.toTable b = {'b': 9, 'c': 13, 'a': 5}.toTable
doAssert a == b doAssert a == b
equalsImpl(s, t) equalsImpl(s, t)
proc rightSize*(count: Natural): int {.inline.} = proc rightSize*(count: Natural): int {.inline.} =
@ -692,6 +677,7 @@ iterator mpairs*[A, B](t: var Table[A, B]): (A, var B) =
for k, v in a.mpairs: for k, v in a.mpairs:
v.add(v[0] + 10) v.add(v[0] + 10)
doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.toTable doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.toTable
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val)
@ -709,6 +695,7 @@ iterator keys*[A, B](t: Table[A, B]): A =
for k in a.keys: for k in a.keys:
a[k].add(99) a[k].add(99)
doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].key if isFilled(t.data[h].hcode): yield t.data[h].key
@ -726,6 +713,7 @@ iterator values*[A, B](t: Table[A, B]): B =
}.toTable }.toTable
for v in a.values: for v in a.values:
doAssert v.len == 4 doAssert v.len == 4
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].val if isFilled(t.data[h].hcode): yield t.data[h].val
@ -744,6 +732,7 @@ iterator mvalues*[A, B](t: var Table[A, B]): var B =
for v in a.mvalues: for v in a.mvalues:
v.add(99) v.add(99)
doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].val if isFilled(t.data[h].hcode): yield t.data[h].val
@ -779,7 +768,7 @@ iterator allValues*[A, B](t: Table[A, B]; key: A): B =
# ------------------------------------------------------------------- # -------------------------------------------------------------------
proc newTable*[A, B](initialSize=64): TableRef[A, B] = proc newTable*[A, B](initialsize = defaultInitialSize): TableRef[A, B] =
## Creates a new ref hash table that is empty. ## Creates a new ref hash table that is empty.
## ##
## ``initialSize`` must be a power of two (default: 64). ## ``initialSize`` must be a power of two (default: 64).
@ -796,6 +785,7 @@ proc newTable*[A, B](initialSize=64): TableRef[A, B] =
let let
a = newTable[int, string]() a = newTable[int, string]()
b = newTable[char, seq[int]]() b = newTable[char, seq[int]]()
new(result) new(result)
result[] = initTable[A, B](initialSize) result[] = initTable[A, B](initialSize)
@ -811,6 +801,7 @@ proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] =
let a = [('a', 5), ('b', 9)] let a = [('a', 5), ('b', 9)]
let b = newTable(a) let b = newTable(a)
assert b == {'a': 5, 'b': 9}.newTable assert b == {'a': 5, 'b': 9}.newTable
new(result) new(result)
result[] = toTable[A, B](pairs) result[] = toTable[A, B](pairs)
@ -842,6 +833,7 @@ proc `[]`*[A, B](t: TableRef[A, B], key: A): var B =
doAssert a['a'] == 5 doAssert a['a'] == 5
doAssertRaises(KeyError): doAssertRaises(KeyError):
echo a['z'] echo a['z']
result = t[][key] result = t[][key]
proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) =
@ -857,6 +849,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) =
a['x'] = 7 a['x'] = 7
a['y'] = 33 a['y'] = 33
doAssert a == {'x': 7, 'y': 33}.newTable doAssert a == {'x': 7, 'y': 33}.newTable
t[][key] = val t[][key] = val
proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
@ -874,6 +867,7 @@ proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.newTable let a = {'a': 5, 'b': 9}.newTable
doAssert a.hasKey('a') == true doAssert a.hasKey('a') == true
doAssert a.hasKey('z') == false doAssert a.hasKey('z') == false
result = t[].hasKey(key) result = t[].hasKey(key)
proc contains*[A, B](t: TableRef[A, B], key: A): bool = proc contains*[A, B](t: TableRef[A, B], key: A): bool =
@ -883,6 +877,7 @@ proc contains*[A, B](t: TableRef[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.newTable let a = {'a': 5, 'b': 9}.newTable
doAssert 'b' in a == true doAssert 'b' in a == true
doAssert a.contains('z') == false doAssert a.contains('z') == false
return hasKey[A, B](t, key) return hasKey[A, B](t, key)
proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool =
@ -902,6 +897,7 @@ proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool =
if a.hasKeyOrPut('z', 50): if a.hasKeyOrPut('z', 50):
a['z'] = 99 a['z'] = 99
doAssert a == {'a': 99, 'b': 9, 'z': 50}.newTable doAssert a == {'a': 99, 'b': 9, 'z': 50}.newTable
t[].hasKeyOrPut(key, val) t[].hasKeyOrPut(key, val)
proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B =
@ -920,6 +916,7 @@ proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B =
let a = {'a': 5, 'b': 9}.newTable let a = {'a': 5, 'b': 9}.newTable
doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('a') == 5
doAssert a.getOrDefault('z') == 0 doAssert a.getOrDefault('z') == 0
getOrDefault(t[], key) getOrDefault(t[], key)
proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B = proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B =
@ -937,6 +934,7 @@ proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B =
let a = {'a': 5, 'b': 9}.newTable let a = {'a': 5, 'b': 9}.newTable
doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('a', 99) == 5
doAssert a.getOrDefault('z', 99) == 99 doAssert a.getOrDefault('z', 99) == 99
getOrDefault(t[], key, default) getOrDefault(t[], key, default)
proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B = proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B =
@ -956,6 +954,7 @@ proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B =
doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('a', 99) == 5
doAssert a.mgetOrPut('z', 99) == 99 doAssert a.mgetOrPut('z', 99) == 99
doAssert a == {'a': 5, 'b': 9, 'z': 99}.newTable doAssert a == {'a': 5, 'b': 9, 'z': 99}.newTable
t[].mgetOrPut(key, val) t[].mgetOrPut(key, val)
proc len*[A, B](t: TableRef[A, B]): int = proc len*[A, B](t: TableRef[A, B]): int =
@ -963,6 +962,7 @@ proc len*[A, B](t: TableRef[A, B]): int =
runnableExamples: runnableExamples:
let a = {'a': 5, 'b': 9}.newTable let a = {'a': 5, 'b': 9}.newTable
doAssert len(a) == 2 doAssert len(a) == 2
result = t.counter result = t.counter
proc add*[A, B](t: TableRef[A, B], key: A, val: B) = proc add*[A, B](t: TableRef[A, B], key: A, val: B) =
@ -988,6 +988,7 @@ proc del*[A, B](t: TableRef[A, B], key: A) =
doAssert a == {'b': 9, 'c': 13}.newTable doAssert a == {'b': 9, 'c': 13}.newTable
a.del('z') a.del('z')
doAssert a == {'b': 9, 'c': 13}.newTable doAssert a == {'b': 9, 'c': 13}.newTable
t[].del(key) t[].del(key)
proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool = proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool =
@ -1012,6 +1013,7 @@ proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool =
doAssert a.take('z', i) == false doAssert a.take('z', i) == false
doAssert a == {'a': 5, 'c': 13}.newTable doAssert a == {'a': 5, 'c': 13}.newTable
doAssert i == 0 doAssert i == 0
result = t[].take(key, val) result = t[].take(key, val)
proc clear*[A, B](t: TableRef[A, B]) = proc clear*[A, B](t: TableRef[A, B]) =
@ -1025,6 +1027,7 @@ proc clear*[A, B](t: TableRef[A, B]) =
doAssert len(a) == 3 doAssert len(a) == 3
clear(a) clear(a)
doAssert len(a) == 0 doAssert len(a) == 0
clearImpl() clearImpl()
proc `$`*[A, B](t: TableRef[A, B]): string = proc `$`*[A, B](t: TableRef[A, B]): string =
@ -1041,6 +1044,7 @@ proc `==`*[A, B](s, t: TableRef[A, B]): bool =
a = {'a': 5, 'b': 9, 'c': 13}.newTable a = {'a': 5, 'b': 9, 'c': 13}.newTable
b = {'b': 9, 'c': 13, 'a': 5}.newTable b = {'b': 9, 'c': 13, 'a': 5}.newTable
doAssert a == b doAssert a == b
if isNil(s): result = isNil(t) if isNil(s): result = isNil(t)
elif isNil(t): result = false elif isNil(t): result = false
else: equalsImpl(s[], t[]) else: equalsImpl(s[], t[])
@ -1089,6 +1093,7 @@ iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) =
for k, v in a.mpairs: for k, v in a.mpairs:
v.add(v[0] + 10) v.add(v[0] + 10)
doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.newTable doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.newTable
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val)
@ -1106,6 +1111,7 @@ iterator keys*[A, B](t: TableRef[A, B]): A =
for k in a.keys: for k in a.keys:
a[k].add(99) a[k].add(99)
doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].key if isFilled(t.data[h].hcode): yield t.data[h].key
@ -1123,6 +1129,7 @@ iterator values*[A, B](t: TableRef[A, B]): B =
}.newTable }.newTable
for v in a.values: for v in a.values:
doAssert v.len == 4 doAssert v.len == 4
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].val if isFilled(t.data[h].hcode): yield t.data[h].val
@ -1140,6 +1147,7 @@ iterator mvalues*[A, B](t: TableRef[A, B]): var B =
for v in a.mvalues: for v in a.mvalues:
v.add(99) v.add(99)
doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable
for h in 0..high(t.data): for h in 0..high(t.data):
if isFilled(t.data[h].hcode): yield t.data[h].val if isFilled(t.data[h].hcode): yield t.data[h].val
@ -1218,7 +1226,7 @@ template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} =
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = proc initOrderedTable*[A, B](initialsize = defaultInitialSize): OrderedTable[A, B] =
## Creates a new ordered hash table that is empty. ## Creates a new ordered hash table that is empty.
## ##
## ``initialSize`` must be a power of two (default: 64). ## ``initialSize`` must be a power of two (default: 64).
@ -1227,6 +1235,9 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] =
## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_
## from this module. ## from this module.
## ##
## Starting from Nim v0.20, tables are initialized by default and it is
## not necessary to call this function explicitly.
##
## See also: ## See also:
## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_ ## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_
## * `newOrderedTable proc<#newOrderedTable,int>`_ for creating an ## * `newOrderedTable proc<#newOrderedTable,int>`_ for creating an
@ -1235,11 +1246,9 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] =
let let
a = initOrderedTable[int, string]() a = initOrderedTable[int, string]()
b = initOrderedTable[char, seq[int]]() b = initOrderedTable[char, seq[int]]()
assert isPowerOfTwo(initialSize) initImpl(result, initialSize)
result.counter = 0
result.first = -1 result.first = -1
result.last = -1 result.last = -1
newSeq(result.data, initialSize)
proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] =
## Creates a new ordered hash table that contains the given ``pairs``. ## Creates a new ordered hash table that contains the given ``pairs``.
@ -1254,6 +1263,7 @@ proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] =
let a = [('a', 5), ('b', 9)] let a = [('a', 5), ('b', 9)]
let b = toOrderedTable(a) let b = toOrderedTable(a)
assert b == {'a': 5, 'b': 9}.toOrderedTable assert b == {'a': 5, 'b': 9}.toOrderedTable
result = initOrderedTable[A, B](rightSize(pairs.len)) result = initOrderedTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result[key] = val for key, val in items(pairs): result[key] = val
@ -1278,6 +1288,7 @@ proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B =
doAssert a['a'] == 5 doAssert a['a'] == 5
doAssertRaises(KeyError): doAssertRaises(KeyError):
echo a['z'] echo a['z']
get(t, key) get(t, key)
proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B= proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B=
@ -1309,6 +1320,7 @@ proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
a['x'] = 7 a['x'] = 7
a['y'] = 33 a['y'] = 33
doAssert a == {'x': 7, 'y': 33}.toOrderedTable doAssert a == {'x': 7, 'y': 33}.toOrderedTable
putImpl(enlarge) putImpl(enlarge)
proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool =
@ -1326,6 +1338,7 @@ proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.toOrderedTable let a = {'a': 5, 'b': 9}.toOrderedTable
doAssert a.hasKey('a') == true doAssert a.hasKey('a') == true
doAssert a.hasKey('z') == false doAssert a.hasKey('z') == false
var hc: Hash var hc: Hash
result = rawGet(t, key, hc) >= 0 result = rawGet(t, key, hc) >= 0
@ -1336,6 +1349,7 @@ proc contains*[A, B](t: OrderedTable[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.toOrderedTable let a = {'a': 5, 'b': 9}.toOrderedTable
doAssert 'b' in a == true doAssert 'b' in a == true
doAssert a.contains('z') == false doAssert a.contains('z') == false
return hasKey[A, B](t, key) return hasKey[A, B](t, key)
proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool = proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool =
@ -1355,6 +1369,7 @@ proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool =
if a.hasKeyOrPut('z', 50): if a.hasKeyOrPut('z', 50):
a['z'] = 99 a['z'] = 99
doAssert a == {'a': 99, 'b': 9, 'z': 50}.toOrderedTable doAssert a == {'a': 99, 'b': 9, 'z': 50}.toOrderedTable
hasKeyOrPutImpl(enlarge) hasKeyOrPutImpl(enlarge)
proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B =
@ -1373,6 +1388,7 @@ proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B =
let a = {'a': 5, 'b': 9}.toOrderedTable let a = {'a': 5, 'b': 9}.toOrderedTable
doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('a') == 5
doAssert a.getOrDefault('z') == 0 doAssert a.getOrDefault('z') == 0
getOrDefaultImpl(t, key) getOrDefaultImpl(t, key)
proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B =
@ -1390,6 +1406,7 @@ proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B =
let a = {'a': 5, 'b': 9}.toOrderedTable let a = {'a': 5, 'b': 9}.toOrderedTable
doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('a', 99) == 5
doAssert a.getOrDefault('z', 99) == 99 doAssert a.getOrDefault('z', 99) == 99
getOrDefaultImpl(t, key, default) getOrDefaultImpl(t, key, default)
proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B =
@ -1409,6 +1426,7 @@ proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B =
doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('a', 99) == 5
doAssert a.mgetOrPut('z', 99) == 99 doAssert a.mgetOrPut('z', 99) == 99
doAssert a == {'a': 5, 'b': 9, 'z': 99}.toOrderedTable doAssert a == {'a': 5, 'b': 9, 'z': 99}.toOrderedTable
mgetOrPutImpl(enlarge) mgetOrPutImpl(enlarge)
proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} =
@ -1416,6 +1434,7 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} =
runnableExamples: runnableExamples:
let a = {'a': 5, 'b': 9}.toOrderedTable let a = {'a': 5, 'b': 9}.toOrderedTable
doAssert len(a) == 2 doAssert len(a) == 2
result = t.counter result = t.counter
proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
@ -1440,6 +1459,7 @@ proc del*[A, B](t: var OrderedTable[A, B], key: A) =
doAssert a == {'b': 9, 'c': 13}.toOrderedTable doAssert a == {'b': 9, 'c': 13}.toOrderedTable
a.del('z') a.del('z')
doAssert a == {'b': 9, 'c': 13}.toOrderedTable doAssert a == {'b': 9, 'c': 13}.toOrderedTable
var n: OrderedKeyValuePairSeq[A, B] var n: OrderedKeyValuePairSeq[A, B]
newSeq(n, len(t.data)) newSeq(n, len(t.data))
var h = t.first var h = t.first
@ -1467,6 +1487,7 @@ proc clear*[A, B](t: var OrderedTable[A, B]) =
doAssert len(a) == 3 doAssert len(a) == 3
clear(a) clear(a)
doAssert len(a) == 0 doAssert len(a) == 0
clearImpl() clearImpl()
t.first = -1 t.first = -1
t.last = -1 t.last = -1
@ -1602,6 +1623,7 @@ iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) =
for k, v in a.mpairs: for k, v in a.mpairs:
v.add(v[0] + 10) v.add(v[0] + 10)
doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.toOrderedTable doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.toOrderedTable
forAllOrderedPairs: forAllOrderedPairs:
yield (t.data[h].key, t.data[h].val) yield (t.data[h].key, t.data[h].val)
@ -1619,6 +1641,7 @@ iterator keys*[A, B](t: OrderedTable[A, B]): A =
for k in a.keys: for k in a.keys:
a[k].add(99) a[k].add(99)
doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.toOrderedTable doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.toOrderedTable
forAllOrderedPairs: forAllOrderedPairs:
yield t.data[h].key yield t.data[h].key
@ -1636,6 +1659,7 @@ iterator values*[A, B](t: OrderedTable[A, B]): B =
}.toOrderedTable }.toOrderedTable
for v in a.values: for v in a.values:
doAssert v.len == 4 doAssert v.len == 4
forAllOrderedPairs: forAllOrderedPairs:
yield t.data[h].val yield t.data[h].val
@ -1666,7 +1690,7 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B =
# --------------------------- OrderedTableRef ------------------------------- # --------------------------- OrderedTableRef -------------------------------
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = proc newOrderedTable*[A, B](initialsize = defaultInitialSize): OrderedTableRef[A, B] =
## Creates a new ordered ref hash table that is empty. ## Creates a new ordered ref hash table that is empty.
## ##
## ``initialSize`` must be a power of two (default: 64). ## ``initialSize`` must be a power of two (default: 64).
@ -1700,6 +1724,7 @@ proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] =
let a = [('a', 5), ('b', 9)] let a = [('a', 5), ('b', 9)]
let b = newOrderedTable(a) let b = newOrderedTable(a)
assert b == {'a': 5, 'b': 9}.newOrderedTable assert b == {'a': 5, 'b': 9}.newOrderedTable
result = newOrderedTable[A, B](rightSize(pairs.len)) result = newOrderedTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result.add(key, val) for key, val in items(pairs): result.add(key, val)
@ -1740,6 +1765,7 @@ proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) =
a['x'] = 7 a['x'] = 7
a['y'] = 33 a['y'] = 33
doAssert a == {'x': 7, 'y': 33}.newOrderedTable doAssert a == {'x': 7, 'y': 33}.newOrderedTable
t[][key] = val t[][key] = val
proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool = proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool =
@ -1757,6 +1783,7 @@ proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.newOrderedTable let a = {'a': 5, 'b': 9}.newOrderedTable
doAssert a.hasKey('a') == true doAssert a.hasKey('a') == true
doAssert a.hasKey('z') == false doAssert a.hasKey('z') == false
result = t[].hasKey(key) result = t[].hasKey(key)
proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool = proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool =
@ -1766,6 +1793,7 @@ proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool =
let a = {'a': 5, 'b': 9}.newOrderedTable let a = {'a': 5, 'b': 9}.newOrderedTable
doAssert 'b' in a == true doAssert 'b' in a == true
doAssert a.contains('z') == false doAssert a.contains('z') == false
return hasKey[A, B](t, key) return hasKey[A, B](t, key)
proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool = proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool =
@ -1785,6 +1813,7 @@ proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool =
if a.hasKeyOrPut('z', 50): if a.hasKeyOrPut('z', 50):
a['z'] = 99 a['z'] = 99
doAssert a == {'a': 99, 'b': 9, 'z': 50}.newOrderedTable doAssert a == {'a': 99, 'b': 9, 'z': 50}.newOrderedTable
result = t[].hasKeyOrPut(key, val) result = t[].hasKeyOrPut(key, val)
proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B =
@ -1803,6 +1832,7 @@ proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B =
let a = {'a': 5, 'b': 9}.newOrderedTable let a = {'a': 5, 'b': 9}.newOrderedTable
doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('a') == 5
doAssert a.getOrDefault('z') == 0 doAssert a.getOrDefault('z') == 0
getOrDefault(t[], key) getOrDefault(t[], key)
proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B = proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B =
@ -1820,6 +1850,7 @@ proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B =
let a = {'a': 5, 'b': 9}.newOrderedTable let a = {'a': 5, 'b': 9}.newOrderedTable
doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('a', 99) == 5
doAssert a.getOrDefault('z', 99) == 99 doAssert a.getOrDefault('z', 99) == 99
getOrDefault(t[], key, default) getOrDefault(t[], key, default)
proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B = proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B =
@ -1839,6 +1870,7 @@ proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B =
doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('a', 99) == 5
doAssert a.mgetOrPut('z', 99) == 99 doAssert a.mgetOrPut('z', 99) == 99
doAssert a == {'a': 5, 'b': 9, 'z': 99}.newOrderedTable doAssert a == {'a': 5, 'b': 9, 'z': 99}.newOrderedTable
result = t[].mgetOrPut(key, val) result = t[].mgetOrPut(key, val)
proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} =
@ -1846,6 +1878,7 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} =
runnableExamples: runnableExamples:
let a = {'a': 5, 'b': 9}.newOrderedTable let a = {'a': 5, 'b': 9}.newOrderedTable
doAssert len(a) == 2 doAssert len(a) == 2
result = t.counter result = t.counter
proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) =
@ -1868,6 +1901,7 @@ proc del*[A, B](t: var OrderedTableRef[A, B], key: A) =
doAssert a == {'b': 9, 'c': 13}.newOrderedTable doAssert a == {'b': 9, 'c': 13}.newOrderedTable
a.del('z') a.del('z')
doAssert a == {'b': 9, 'c': 13}.newOrderedTable doAssert a == {'b': 9, 'c': 13}.newOrderedTable
t[].del(key) t[].del(key)
proc clear*[A, B](t: var OrderedTableRef[A, B]) = proc clear*[A, B](t: var OrderedTableRef[A, B]) =
@ -1880,6 +1914,7 @@ proc clear*[A, B](t: var OrderedTableRef[A, B]) =
doAssert len(a) == 3 doAssert len(a) == 3
clear(a) clear(a)
doAssert len(a) == 0 doAssert len(a) == 0
clear(t[]) clear(t[])
proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int, order = SortOrder.Ascending) = proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int, order = SortOrder.Ascending) =
@ -1899,6 +1934,7 @@ proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int, order =
doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable
a.sort(system.cmp, order=SortOrder.Descending) a.sort(system.cmp, order=SortOrder.Descending)
doAssert a == {'c': 0, 'b': 20, 'a': 10}.newOrderedTable doAssert a == {'c': 0, 'b': 20, 'a': 10}.newOrderedTable
t[].sort(cmp, order=order) t[].sort(cmp, order=order)
proc `$`*[A, B](t: OrderedTableRef[A, B]): string = proc `$`*[A, B](t: OrderedTableRef[A, B]): string =
@ -1915,6 +1951,7 @@ proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool =
a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable
b = {'b': 9, 'c': 13, 'a': 5}.newOrderedTable b = {'b': 9, 'c': 13, 'a': 5}.newOrderedTable
doAssert a != b doAssert a != b
if isNil(s): result = isNil(t) if isNil(s): result = isNil(t)
elif isNil(t): result = false elif isNil(t): result = false
else: result = s[] == t[] else: result = s[] == t[]
@ -1964,6 +2001,7 @@ iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) =
for k, v in a.mpairs: for k, v in a.mpairs:
v.add(v[0] + 10) v.add(v[0] + 10)
doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.newOrderedTable doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.newOrderedTable
forAllOrderedPairs: forAllOrderedPairs:
yield (t.data[h].key, t.data[h].val) yield (t.data[h].key, t.data[h].val)
@ -1981,6 +2019,7 @@ iterator keys*[A, B](t: OrderedTableRef[A, B]): A =
for k in a.keys: for k in a.keys:
a[k].add(99) a[k].add(99)
doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable
forAllOrderedPairs: forAllOrderedPairs:
yield t.data[h].key yield t.data[h].key
@ -1998,6 +2037,7 @@ iterator values*[A, B](t: OrderedTableRef[A, B]): B =
}.newOrderedTable }.newOrderedTable
for v in a.values: for v in a.values:
doAssert v.len == 4 doAssert v.len == 4
forAllOrderedPairs: forAllOrderedPairs:
yield t.data[h].val yield t.data[h].val
@ -2016,6 +2056,7 @@ iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B =
for v in a.mvalues: for v in a.mvalues:
v.add(99) v.add(99)
doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable
forAllOrderedPairs: forAllOrderedPairs:
yield t.data[h].val yield t.data[h].val
@ -2046,7 +2087,7 @@ type
# ------------------------------ helpers --------------------------------- # ------------------------------ helpers ---------------------------------
proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], proc ctRawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]],
key: A, val: int) = key: A, val: int) =
var h: Hash = hash(key) and high(data) var h: Hash = hash(key) and high(data)
while data[h].val != 0: h = nextTry(h, high(data)) while data[h].val != 0: h = nextTry(h, high(data))
@ -2057,10 +2098,12 @@ proc enlarge[A](t: var CountTable[A]) =
var n: seq[tuple[key: A, val: int]] var n: seq[tuple[key: A, val: int]]
newSeq(n, len(t.data) * growthFactor) newSeq(n, len(t.data) * growthFactor)
for i in countup(0, high(t.data)): for i in countup(0, high(t.data)):
if t.data[i].val != 0: rawInsert(t, n, t.data[i].key, t.data[i].val) if t.data[i].val != 0: ctRawInsert(t, n, t.data[i].key, t.data[i].val)
swap(t.data, n) swap(t.data, n)
proc rawGet[A](t: CountTable[A], key: A): int = proc rawGet[A](t: CountTable[A], key: A): int =
if t.data.len == 0:
return -1
var h: Hash = hash(key) and high(t.data) # start with real hash value var h: Hash = hash(key) and high(t.data) # start with real hash value
while t.data[h].val != 0: while t.data[h].val != 0:
if t.data[h].key == key: return h if t.data[h].key == key: return h
@ -2075,7 +2118,7 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1)
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
proc initCountTable*[A](initialSize=64): CountTable[A] = proc initCountTable*[A](initialsize = defaultInitialSize): CountTable[A] =
## Creates a new count table that is empty. ## Creates a new count table that is empty.
## ##
## ``initialSize`` must be a power of two (default: 64). ## ``initialSize`` must be a power of two (default: 64).
@ -2084,13 +2127,14 @@ proc initCountTable*[A](initialSize=64): CountTable[A] =
## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_
## from this module. ## from this module.
## ##
## Starting from Nim v0.20, tables are initialized by default and it is
## not necessary to call this function explicitly.
##
## See also: ## See also:
## * `toCountTable proc<#toCountTable,openArray[A]>`_ ## * `toCountTable proc<#toCountTable,openArray[A]>`_
## * `newCountTable proc<#newCountTable,int>`_ for creating a ## * `newCountTable proc<#newCountTable,int>`_ for creating a
## `CountTableRef` ## `CountTableRef`
assert isPowerOfTwo(initialSize) initImpl(result, initialSize)
result.counter = 0
newSeq(result.data, initialSize)
proc toCountTable*[A](keys: openArray[A]): CountTable[A] = proc toCountTable*[A](keys: openArray[A]): CountTable[A] =
## Creates a new count table with every member of a container ``keys`` ## Creates a new count table with every member of a container ``keys``
@ -2130,9 +2174,7 @@ proc `[]=`*[A](t: var CountTable[A], key: A, val: int) =
if h >= 0: if h >= 0:
t.data[h].val = val t.data[h].val = val
else: else:
if mustRehash(len(t.data), t.counter): enlarge(t) insertImpl()
rawInsert(t, t.data, key, val)
inc(t.counter)
proc inc*[A](t: var CountTable[A], key: A, val = 1) = proc inc*[A](t: var CountTable[A], key: A, val = 1) =
## Increments ``t[key]`` by ``val`` (default: 1). ## Increments ``t[key]`` by ``val`` (default: 1).
@ -2141,14 +2183,13 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) =
a.inc('a') a.inc('a')
a.inc('b', 10) a.inc('b', 10)
doAssert a == toCountTable("aaabbbbbbbbbbb") doAssert a == toCountTable("aaabbbbbbbbbbb")
var index = rawGet(t, key) var index = rawGet(t, key)
if index >= 0: if index >= 0:
inc(t.data[index].val, val) inc(t.data[index].val, val)
if t.data[index].val == 0: dec(t.counter) if t.data[index].val == 0: dec(t.counter)
else: else:
if mustRehash(len(t.data), t.counter): enlarge(t) insertImpl()
rawInsert(t, t.data, key, val)
inc(t.counter)
proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] =
## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n)
@ -2229,6 +2270,7 @@ proc sort*[A](t: var CountTable[A], order = SortOrder.Descending) =
doAssert toSeq(a.values) == @[5, 2, 2, 1, 1] doAssert toSeq(a.values) == @[5, 2, 2, 1, 1]
a.sort(SortOrder.Ascending) a.sort(SortOrder.Ascending)
doAssert toSeq(a.values) == @[1, 1, 2, 2, 5] doAssert toSeq(a.values) == @[1, 1, 2, 2, 5]
t.data.sort(cmp=ctCmp, order=order) t.data.sort(cmp=ctCmp, order=order)
proc merge*[A](s: var CountTable[A], t: CountTable[A]) = proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
@ -2238,6 +2280,7 @@ proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
let b = toCountTable("bcc") let b = toCountTable("bcc")
a.merge(b) a.merge(b)
doAssert a == toCountTable("aaabbbccc") doAssert a == toCountTable("aaabbbccc")
for key, value in t: for key, value in t:
s.inc(key, value) s.inc(key, value)
@ -2248,6 +2291,7 @@ proc merge*[A](s, t: CountTable[A]): CountTable[A] =
a = toCountTable("aaabbc") a = toCountTable("aaabbc")
b = toCountTable("bcc") b = toCountTable("bcc")
doAssert merge(a, b) == toCountTable("aaabbbccc") doAssert merge(a, b) == toCountTable("aaabbbccc")
result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len))) result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len)))
for table in @[s, t]: for table in @[s, t]:
for key, value in table: for key, value in table:
@ -2306,6 +2350,7 @@ iterator mpairs*[A](t: var CountTable[A]): (A, var int) =
for k, v in mpairs(a): for k, v in mpairs(a):
v = 2 v = 2
doAssert a == toCountTable("aabbccddrr") doAssert a == toCountTable("aabbccddrr")
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val)
@ -2320,6 +2365,7 @@ iterator keys*[A](t: CountTable[A]): A =
for k in keys(a): for k in keys(a):
a[k] = 2 a[k] = 2
doAssert a == toCountTable("aabbccddrr") doAssert a == toCountTable("aabbccddrr")
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].key if t.data[h].val != 0: yield t.data[h].key
@ -2334,6 +2380,7 @@ iterator values*[A](t: CountTable[A]): int =
let a = toCountTable("abracadabra") let a = toCountTable("abracadabra")
for v in values(a): for v in values(a):
assert v < 10 assert v < 10
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].val if t.data[h].val != 0: yield t.data[h].val
@ -2349,6 +2396,7 @@ iterator mvalues*[A](t: var CountTable[A]): var int =
for v in mvalues(a): for v in mvalues(a):
v = 2 v = 2
doAssert a == toCountTable("aabbccddrr") doAssert a == toCountTable("aabbccddrr")
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].val if t.data[h].val != 0: yield t.data[h].val
@ -2364,7 +2412,7 @@ iterator mvalues*[A](t: var CountTable[A]): var int =
proc inc*[A](t: CountTableRef[A], key: A, val = 1) proc inc*[A](t: CountTableRef[A], key: A, val = 1)
proc newCountTable*[A](initialSize=64): CountTableRef[A] = proc newCountTable*[A](initialsize = defaultInitialSize): CountTableRef[A] =
## Creates a new ref count table that is empty. ## Creates a new ref count table that is empty.
## ##
## ``initialSize`` must be a power of two (default: 64). ## ``initialSize`` must be a power of two (default: 64).
@ -2493,6 +2541,7 @@ proc merge*[A](s, t: CountTableRef[A]) =
b = newCountTable("bcc") b = newCountTable("bcc")
a.merge(b) a.merge(b)
doAssert a == newCountTable("aaabbbccc") doAssert a == newCountTable("aaabbbccc")
s[].merge(t[]) s[].merge(t[])
proc `$`*[A](t: CountTableRef[A]): string = proc `$`*[A](t: CountTableRef[A]): string =
@ -2551,6 +2600,7 @@ iterator mpairs*[A](t: CountTableRef[A]): (A, var int) =
for k, v in mpairs(a): for k, v in mpairs(a):
v = 2 v = 2
doAssert a == newCountTable("aabbccddrr") doAssert a == newCountTable("aabbccddrr")
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val)
@ -2565,6 +2615,7 @@ iterator keys*[A](t: CountTableRef[A]): A =
for k in keys(a): for k in keys(a):
a[k] = 2 a[k] = 2
doAssert a == newCountTable("aabbccddrr") doAssert a == newCountTable("aabbccddrr")
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].key if t.data[h].val != 0: yield t.data[h].key
@ -2579,6 +2630,7 @@ iterator values*[A](t: CountTableRef[A]): int =
let a = newCountTable("abracadabra") let a = newCountTable("abracadabra")
for v in values(a): for v in values(a):
assert v < 10 assert v < 10
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].val if t.data[h].val != 0: yield t.data[h].val
@ -2593,6 +2645,7 @@ iterator mvalues*[A](t: CountTableRef[A]): var int =
for v in mvalues(a): for v in mvalues(a):
v = 2 v = 2
doAssert a == newCountTable("aabbccddrr") doAssert a == newCountTable("aabbccddrr")
for h in 0..high(t.data): for h in 0..high(t.data):
if t.data[h].val != 0: yield t.data[h].val if t.data[h].val != 0: yield t.data[h].val
@ -2813,3 +2866,99 @@ when isMainModule:
doAssert "test1" == orf.getOrDefault("test1", "test1") doAssert "test1" == orf.getOrDefault("test1", "test1")
orf["test2"] = "test2" orf["test2"] = "test2"
doAssert "test2" == orf.getOrDefault("test2", "test1") doAssert "test2" == orf.getOrDefault("test2", "test1")
block tableWithoutInit:
var
a: Table[string, int]
b: Table[string, int]
c: Table[string, int]
d: Table[string, int]
e: Table[string, int]
a["a"] = 7
doAssert a.hasKey("a")
doAssert a.len == 1
doAssert a["a"] == 7
a["a"] = 9
doAssert a.len == 1
doAssert a["a"] == 9
doAssert b.hasKeyOrPut("b", 5) == false
doAssert b.hasKey("b")
doAssert b.hasKeyOrPut("b", 8)
doAssert b["b"] == 5
doAssert c.getOrDefault("a") == 0
doAssert c.getOrDefault("a", 3) == 3
c["a"] = 6
doAssert c.getOrDefault("a", 3) == 6
doAssert d.mgetOrPut("a", 3) == 3
doAssert d.mgetOrPut("a", 6) == 3
var x = 99
doAssert e.take("a", x) == false
doAssert x == 99
e["a"] = 77
doAssert e.take("a", x)
doAssert x == 77
block orderedTableWithoutInit:
var
a: OrderedTable[string, int]
b: OrderedTable[string, int]
c: OrderedTable[string, int]
d: OrderedTable[string, int]
a["a"] = 7
doAssert a.hasKey("a")
doAssert a.len == 1
doAssert a["a"] == 7
a["a"] = 9
doAssert a.len == 1
doAssert a["a"] == 9
doAssert b.hasKeyOrPut("b", 5) == false
doAssert b.hasKey("b")
doAssert b.hasKeyOrPut("b", 8)
doAssert b["b"] == 5
doAssert c.getOrDefault("a") == 0
doAssert c.getOrDefault("a", 3) == 3
c["a"] = 6
doAssert c.getOrDefault("a", 3) == 6
doAssert d.mgetOrPut("a", 3) == 3
doAssert d.mgetOrPut("a", 6) == 3
block countTableWithoutInit:
var
a: CountTable[string]
b: CountTable[string]
c: CountTable[string]
d: CountTable[string]
e: CountTable[string]
a["a"] = 7
doAssert a.hasKey("a")
doAssert a.len == 1
doAssert a["a"] == 7
a["a"] = 9
doAssert a.len == 1
doAssert a["a"] == 9
doAssert b["b"] == 0
b.inc("b")
doAssert b["b"] == 1
doAssert c.getOrDefault("a") == 0
doAssert c.getOrDefault("a", 3) == 3
c["a"] = 6
doAssert c.getOrDefault("a", 3) == 6
e["f"] = 3
merge(d, e)
doAssert d.hasKey("f")
d.inc("f")
merge(d, e)
doAssert d["f"] == 7

View file

@ -515,6 +515,8 @@ const disabledFilesDefault = @[
"LockFreeHash.nim", "LockFreeHash.nim",
"sharedstrings.nim", "sharedstrings.nim",
"tableimpl.nim", "tableimpl.nim",
"setimpl.nim",
"hashcommon.nim",
# Error: undeclared identifier: 'hasThreadSupport' # Error: undeclared identifier: 'hasThreadSupport'
"ioselectors_epoll.nim", "ioselectors_epoll.nim",