# 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