nondescript/types/hashtable.nim

121 lines
3.2 KiB
Nim

# The hash table implementation for string interning
import strformat
const tableMaxLoad = 0.75
const tableInitSize = 8
type
EntryStatus = enum
esNil, esAlive, esTombstone
Entry*[U, V] = object
entryStatus: EntryStatus
key: U
value: V
Table*[U, V] = object
count: int
cap: int
entries: ptr UncheckedArray[Entry[U, V]]
proc newTable*[U, V]: Table[U, V] =
result.cap = 0
result.count = 0
proc free*[U, V](tbl: var Table[U, V]) =
if tbl.entries != nil:
dealloc(tbl.entries)
proc isNil[U, V](entry: ptr Entry[U, V]): bool {.inline.} =
entry[].entryStatus == esNil
proc isTombstone[U, V](entry: ptr Entry[U, V]): bool {.inline.} =
entry[].entryStatus == esTombstone
proc isAlive[U, V](entry: ptr Entry[U, V]): bool {.inline.} =
entry[].entryStatus == esAlive
proc findEntry[U, V](entries: ptr UncheckedArray[Entry[U, V]], cap: int, key: U): ptr Entry[U, V] =
var index = key.hash() mod cap # TODO replace mod with sth better
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
if entry.isNil():
return if tombstone != nil: tombstone else: entry
elif entry.isTombstone(): # TODO: optimalization: case statement
if tombstone == nil:
tombstone = entry
elif entry[].key == key:
return entry
index = (index + 1) mod cap # TODO replace mod with sth better
proc grow[U, V](tbl: var Table[U, V]): int {.inline.} =
## Calculates the new capacity
if tbl.cap > 0:
tbl.cap * 2
else:
tableInitSize
proc adjustCapacity[U, V](tbl: var Table[U, V], newcap: int) =
let entries: ptr UncheckedArray[Entry[U, V]] = cast[ptr UncheckedArray[Entry[U, V]]](alloc0(newcap * sizeof(Entry[U, V])))
tbl.count = 0
for i in countup(0, tbl.cap-1):
let entry = tbl.entries[i]
if entry.entryStatus == esAlive:
var dest = findEntry(entries, newcap, entry.key)
dest[].key = entry.key
dest[].value = entry.value
dest[].entryStatus = esAlive
tbl.count.inc
if tbl.entries != nil:
dealloc(tbl.entries)
tbl.entries = entries
tbl.cap = newcap
proc tableSet*[U, V](tbl: var Table[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 + 1 > int(tbl.cap.float * tableMaxLoad):
let cap = tbl.grow()
tbl.adjustCapacity(cap)
let entry: ptr Entry[U, V] = findEntry(tbl.entries, tbl.cap, 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: Table[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, tbl.cap, key)
if not entry.isAlive():
return false
val = entry[].value
return true
proc tableDelete*[U, V](tbl: Table[U, V], key: U): bool =
if tbl.count == 0:
return false
let entry = findEntry(tbl.entries, tbl.cap, key)
if not entry.isAlive():
return false
entry[].entryStatus = esTombstone
return true