nondescript/src/ndspkg/types/ndtable.nim

129 lines
3.5 KiB
Nim

# The hash table implementation for table types
import ndstring
import bitops
const tableMaxLoad = 0.75
const tableInitSize = 8
type
EntryStatus = enum
esNil, esAlive, esTombstone
Entry[U, V] = object
entryStatus: EntryStatus
key: U
value: V
NdTable*[U, V] = ptr object
count: uint32
cap: uint32
entries: UncheckedArray[Entry[U, V]]
template allocSize[U, V](tbl: NdTable[U, V], cap: int): int =
8 + cap * sizeof(Entry[U, V])
proc newNdTable*[U, V]: NdTable[U, V] =
result = cast[NdTable[U,V]](result.allocSize(tableInitSize).alloc0())
result[].cap = tableInitSize
result[].count = 0
proc free*[U, V](tbl: var NdTable[U, V]) =
dealloc(tbl)
proc findEntry[U, V](entries: ptr UncheckedArray[Entry[U, V]], cap: int, key: U): ptr Entry[U, V] =
mixin fnv1a, equal
var index = key.fnv1a().bitand(cap - 1)
var tombstone: ptr Entry[U, V] = nil
while true:
let entry: ptr Entry[U, V] = entries[index].addr # TODO: check the performance impact of this line
case entry[].entryStatus:
of esNil:
return if tombstone != nil: tombstone else: entry
of esTombstone:
if tombstone == nil:
tombstone = entry
of esAlive:
if entry[].key.equal(key):
return entry
index = (index + 1).bitand(cap - 1)
proc grow[U, V](tbl: var NdTable[U, V]): int {.inline.} =
## Calculates the new capacity
tbl[].cap.int * 2
proc adjustCapacity[U, V](tbl: var NdTable[U, V], newcap: int) =
let tblnew = cast[NdTable[U,V]](tbl.allocSize(newcap).alloc0())
tblnew[].count = tbl.count
tblnew[].cap = newcap.uint32
for i in countup(0, tbl.cap.int - 1):
let entry = tbl[].entries[i]
if entry.entryStatus == esAlive:
var dest = findEntry(tblnew.entries.addr, newcap, entry.key)
dest[].key = entry.key
dest[].value = entry.value
dest[].entryStatus = esAlive
tbl.count.inc
if tbl != nil:
dealloc(tbl)
tbl = tblnew
proc tableSet*[U, V](tbl: var NdTable[U, V], key: U, val: V): bool =
## Returns false if new value is entered
## True if the value entered already existed and is overwritten
if tbl.count.int + 1 > int(tbl.cap.float * tableMaxLoad):
let cap = tbl.grow()
tbl.adjustCapacity(cap)
let entry: ptr Entry[U, V] = findEntry(tbl.entries.addr, tbl.cap.int, key)
let status = entry[].entryStatus
if status == esNil:
tbl.count.inc
elif status == esAlive:
result = true
entry[].key = key
entry[].value = val
entry[].entryStatus = esAlive
proc tableGet*[U, V](tbl: NdTable[U, V], key: U, val: var V): bool =
## Returns false if not in table
## Returns true if in the table, sets value to val
if tbl.count == 0:
return false
let entry = findEntry(tbl.entries.addr, tbl.cap.int, key)
if not (entry[].entryStatus == esAlive):
return false
val = entry[].value
return true
proc tableDelete*[U, V](tbl: NdTable[U, V], key: U): bool =
if tbl.count == 0:
return false
let entry = findEntry(tbl.entries.addr, tbl.cap.int, key)
if entry[].entryStatus != esAlive:
return false
entry[].entryStatus = esTombstone
return true
proc `$`*[U, V](tbl: NdTable[U, V]): string =
result = "@{ "
for i in countup(0, tbl.cap.int - 1):
let entry = tbl[].entries[i]
if entry.entryStatus == esAlive:
mixin `$`
result &= $entry.key
result &= " = "
result &= $entry.value
result &= ", "
result[^2] = ' '
result[^1] = '}'
proc getLength*[U, V](tbl: NdTable[U, V]): int =
tbl.count.int