Skip to content

Commit

Permalink
tables/sharedtables/intsets/etc: fix #13496, #13504, #13505; add lots…
Browse files Browse the repository at this point in the history
… of tests (#13498) [backport]

* fix #13496 handle tombstones
* add test
* more tests
* fix #13504; add SharedTable tests
* fix ##13505 intsets.missingOrExcl silently gave wrong results sometimes
* add test for tintsets
  • Loading branch information
timotheecour authored Feb 26, 2020
1 parent 9a93f73 commit 42dad3a
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 41 deletions.
22 changes: 11 additions & 11 deletions lib/pure/collections/intsets.nim
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,15 @@ proc excl*(s: var IntSet, other: IntSet) =

for item in other: excl(s, item)

proc len*(s: IntSet): int {.inline.} =
## Returns the number of elements in `s`.
if s.elems < s.a.len:
result = s.elems
else:
result = 0
for _ in s:
inc(result)

proc missingOrExcl*(s: var IntSet, key: int): bool =
## Excludes `key` in the set `s` and tells if `key` was already missing from `s`.
##
Expand All @@ -348,9 +357,9 @@ proc missingOrExcl*(s: var IntSet, key: int): bool =
assert a.missingOrExcl(5) == false
assert a.missingOrExcl(5) == true

var count = s.elems
var count = s.len
exclImpl(s, key)
result = count == s.elems
result = count == s.len

proc clear*(result: var IntSet) =
## Clears the IntSet back to an empty state.
Expand Down Expand Up @@ -514,15 +523,6 @@ proc disjoint*(s1, s2: IntSet): bool =
return false
return true

proc len*(s: IntSet): int {.inline.} =
## Returns the number of elements in `s`.
if s.elems < s.a.len:
result = s.elems
else:
result = 0
for _ in s:
inc(result)

proc card*(s: IntSet): int {.inline.} =
## Alias for `len() <#len,IntSet>`_.
result = s.len()
Expand Down
6 changes: 3 additions & 3 deletions lib/pure/collections/setimpl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ proc enlarge[A](s: var HashSet[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):
if isFilledAndValid(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)

Expand Down Expand Up @@ -112,7 +112,7 @@ proc enlarge[A](s: var OrderedSet[A]) =
swap(s.data, n)
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
if isFilled(n[h].hcode): # should be isFilledAndValid once tombstones are used
var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode)
rawInsert(s, s.data, n[h].key, n[h].hcode, j)
h = nxt
Expand All @@ -130,7 +130,7 @@ proc exclImpl[A](s: var OrderedSet[A], key: A): bool {.inline.} =
result = true
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
if isFilled(n[h].hcode): # should be isFilledAndValid once tombstones are used
if n[h].hcode == hc and n[h].key == key:
dec s.counter
result = false
Expand Down
5 changes: 5 additions & 0 deletions lib/pure/collections/sharedtables.nim
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ proc del*[A, B](t: var SharedTable[A, B], key: A) =
withLock t:
delImpl()

proc len*[A, B](t: var SharedTable[A, B]): int =
## number of elements in `t`
withLock t:
result = t.counter

proc init*[A, B](t: var SharedTable[A, B], initialSize = 64) =
## creates a new hash table that is empty.
##
Expand Down
22 changes: 16 additions & 6 deletions lib/pure/collections/tableimpl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,23 @@ template clearImpl() {.dirty.} =
t.data[i].val = default(type(t.data[i].val))
t.counter = 0

template ctAnd(a, b): bool =
# pending https://github.com/nim-lang/Nim/issues/13502
when a:
when b: true
else: false
else: false

template initImpl(result: typed, size: int) =
assert isPowerOfTwo(size)
result.counter = 0
newSeq(result.data, size)
when compiles(result.first):
result.first = -1
result.last = -1
when ctAnd(declared(SharedTable), type(result) is SharedTable):
init(result, size)
else:
assert isPowerOfTwo(size)
result.counter = 0
newSeq(result.data, size)
when compiles(result.first):
result.first = -1
result.last = -1

template insertImpl() = # for CountTable
checkIfInitialized()
Expand Down
14 changes: 9 additions & 5 deletions lib/pure/collections/tables.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ iterator pairs*[A, B](t: TableRef[A, B]): (A, B) =
## # value: [1, 5, 7, 9]
let L = len(t)
for h in 0 .. high(t.data):
if isFilled(t.data[h].hcode):
if isFilledAndValid(t.data[h].hcode):
yield (t.data[h].key, t.data[h].val)
assert(len(t) == L, "the length of the table changed while iterating over it")

Expand All @@ -1140,7 +1140,7 @@ iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) =

let L = len(t)
for h in 0 .. high(t.data):
if isFilled(t.data[h].hcode):
if isFilledAndValid(t.data[h].hcode):
yield (t.data[h].key, t.data[h].val)
assert(len(t) == L, "the length of the table changed while iterating over it")

Expand All @@ -1161,7 +1161,7 @@ iterator keys*[A, B](t: TableRef[A, B]): A =

let L = len(t)
for h in 0 .. high(t.data):
if isFilled(t.data[h].hcode):
if isFilledAndValid(t.data[h].hcode):
yield t.data[h].key
assert(len(t) == L, "the length of the table changed while iterating over it")

Expand All @@ -1182,7 +1182,7 @@ iterator values*[A, B](t: TableRef[A, B]): B =

let L = len(t)
for h in 0 .. high(t.data):
if isFilled(t.data[h].hcode):
if isFilledAndValid(t.data[h].hcode):
yield t.data[h].val
assert(len(t) == L, "the length of the table changed while iterating over it")

Expand All @@ -1203,7 +1203,7 @@ iterator mvalues*[A, B](t: TableRef[A, B]): var B =

let L = len(t)
for h in 0 .. high(t.data):
if isFilled(t.data[h].hcode):
if isFilledAndValid(t.data[h].hcode):
yield t.data[h].val
assert(len(t) == L, "the length of the table changed while iterating over it")

Expand Down Expand Up @@ -1282,6 +1282,10 @@ template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} =
var h = t.first
while h >= 0:
var nxt = t.data[h].next
# For OrderedTable/OrderedTableRef, isFilled is ok because `del` is O(n)
# and doesn't create tombsones, but if it does start using tombstones,
# carefully replace `isFilled` by `isFilledAndValid` as appropriate for these
# table types only, ditto with `OrderedSet`.
if isFilled(t.data[h].hcode):
yieldStmt
h = nxt
Expand Down
45 changes: 43 additions & 2 deletions tests/collections/ttables.nim
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,14 @@ block tableconstr:
block ttables2:
proc TestHashIntInt() =
var tab = initTable[int,int]()
for i in 1..1_000_000:
when defined(nimTestsTablesDisableSlow):
# helps every single time when this test needs to be debugged
let n = 1_000
else:
let n = 1_000_000
for i in 1..n:
tab[i] = i
for i in 1..1_000_000:
for i in 1..n:
var x = tab[i]
if x != i : echo "not found ", i

Expand Down Expand Up @@ -395,3 +400,39 @@ block tablesref:
orderedTableSortTest()
echo "3"


block: # https://github.com/nim-lang/Nim/issues/13496
template testDel(body) =
block:
body
when t is CountTable|CountTableRef:
t.inc(15, 1)
t.inc(19, 2)
t.inc(17, 3)
t.inc(150, 4)
t.del(150)
else:
t[15] = 1
t[19] = 2
t[17] = 3
t[150] = 4
t.del(150)
doAssert t.len == 3
doAssert sortedItems(t.values) == @[1, 2, 3]
doAssert sortedItems(t.keys) == @[15, 17, 19]
doAssert sortedPairs(t) == @[(15, 1), (17, 3), (19, 2)]
var s = newSeq[int]()
for v in t.values: s.add(v)
assert s.len == 3
doAssert sortedItems(s) == @[1, 2, 3]
when t is OrderedTable|OrderedTableRef:
doAssert toSeq(t.keys) == @[15, 19, 17]
doAssert toSeq(t.values) == @[1,2,3]
doAssert toSeq(t.pairs) == @[(15, 1), (19, 2), (17, 3)]

testDel(): (var t: Table[int, int])
testDel(): (let t = newTable[int, int]())
testDel(): (var t: OrderedTable[int, int])
testDel(): (let t = newOrderedTable[int, int]())
testDel(): (var t: CountTable[int])
testDel(): (let t = newCountTable[int]())
72 changes: 64 additions & 8 deletions tests/sets/tsets_various.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ set is empty

import sets, hashes

from sequtils import toSeq
from algorithm import sorted

proc sortedPairs[T](t: T): auto = toSeq(t.pairs).sorted
template sortedItems(t: untyped): untyped = sorted(toSeq(t))

block tsetpop:
var a = initSet[int]()
var a = initHashSet[int]()
for i in 1..1000:
a.incl(i)
doAssert len(a) == 1000
Expand Down Expand Up @@ -50,7 +55,7 @@ block tsets2:
"80"]

block tableTest1:
var t = initSet[tuple[x, y: int]]()
var t = initHashSet[tuple[x, y: int]]()
t.incl((0,0))
t.incl((1,0))
assert(not t.containsOrIncl((0,1)))
Expand All @@ -63,7 +68,7 @@ block tsets2:
# "{(x: 0, y: 0), (x: 0, y: 1), (x: 1, y: 0), (x: 1, y: 1)}")

block setTest2:
var t = initSet[string]()
var t = initHashSet[string]()
t.incl("test")
t.incl("111")
t.incl("123")
Expand Down Expand Up @@ -102,9 +107,9 @@ block tsets2:

block tsets3:
let
s1: HashSet[int] = toSet([1, 2, 4, 8, 16])
s2: HashSet[int] = toSet([1, 2, 3, 5, 8])
s3: HashSet[int] = toSet([3, 5, 7])
s1: HashSet[int] = toHashSet([1, 2, 4, 8, 16])
s2: HashSet[int] = toHashSet([1, 2, 3, 5, 8])
s3: HashSet[int] = toHashSet([3, 5, 7])

block union:
let
Expand Down Expand Up @@ -172,7 +177,7 @@ block tsets3:
assert i in s1_s3 xor i in s1
assert i in s2_s3 xor i in s2

assert((s3 -+- s3) == initSet[int]())
assert((s3 -+- s3) == initHashSet[int]())
assert((s3 -+- s1) == s1_s3)

block difference:
Expand All @@ -191,10 +196,61 @@ block tsets3:
for i in s2:
assert i in s2_s3 xor i in s3

assert((s2 - s2) == initSet[int]())
assert((s2 - s2) == initHashSet[int]())

block disjoint:
assert(not disjoint(s1, s2))
assert disjoint(s1, s3)
assert(not disjoint(s2, s3))
assert(not disjoint(s2, s2))

block: # https://github.com/nim-lang/Nim/issues/13496
template testDel(body) =
block:
body
t.incl(15)
t.incl(19)
t.incl(17)
t.incl(150)
t.excl(150)
doAssert t.len == 3
doAssert sortedItems(t) == @[15, 17, 19]
var s = newSeq[int]()
for v in t: s.add(v)
assert s.len == 3
doAssert sortedItems(s) == @[15, 17, 19]
when t is OrderedSet:
doAssert sortedPairs(t) == @[(a: 0, b: 15), (a: 1, b: 19), (a: 2, b: 17)]
doAssert toSeq(t) == @[15, 19, 17]

testDel(): (var t: HashSet[int])
testDel(): (var t: OrderedSet[int])

block: # test correctness after a number of inserts/deletes
template testDel(body) =
block:
body
var expected: seq[int]
let n = 100
let n2 = n*2
for i in 0..<n:
t.incl(i)
for i in 0..<n:
if i mod 3 == 0:
t.excl(i)
for i in n..<n2:
t.incl(i)
for i in 0..<n2:
if i mod 7 == 0:
t.excl(i)

for i in 0..<n2:
if (i>=n or i mod 3 != 0) and i mod 7 != 0:
expected.add i

for i in expected: doAssert i in t
doAssert t.len == expected.len
doAssert sortedItems(t) == expected

testDel(): (var t: HashSet[int])
testDel(): (var t: OrderedSet[int])
Loading

0 comments on commit 42dad3a

Please sign in to comment.