Files
TransPyC/includes/hashtable.vp

241 lines
9.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# hashtable - Open-addressing hash table library
# Linear probing for collision resolution, supports auto-resizing and tombstone deletion
# Key type fixed as str (i8*), value type is void* (generic pointer)
import t, c
import string
import memhub
from stdint import *
# Empty slot marker (hash == 0)
_HT_EMPTY: t.CDefine = 0
# Tombstone slot marker (hash == 1)
_HT_TOMBSTONE: t.CDefine = 1
# Default initial capacity (must be power of two)
_HT_INIT_CAP: t.CDefine = 16
# Slot memory layout: hash(8) + key pointer(8) + value pointer(8) = 24 bytes
_HT_SLOT_SIZE: t.CDefine = 24
# FNV-1a 64-bit constants
_FNV_OFFSET: t.CUInt64T = 0xcbf29ce484222325
_FNV_PRIME: t.CUInt64T = 0x100000001b3
def _fnv1a_hash(key: str) -> t.CUInt64T:
"""FNV-1a 64-bit hash function."""
h: t.CUInt64T = _FNV_OFFSET
k: str = key
while k[0] != 0:
h = h ^ t.CUInt64T(k[0])
h = h * _FNV_PRIME
k += 1
# Ensure h >= 2 (avoids empty slot marker 0 and tombstone marker 1)
if h < 2:
h = 2
return h
class HashTable:
"""Open-addressing hash table using linear probing for collision resolution.
Key type fixed as str (i8*), value type is void* (generic pointer).
Memory is managed via the mbuddy allocator. Automatically resizes when load factor exceeds 0.75.
Usage:
mb: memhub.MemBuddy = memhub.MemBuddy(arena, arena_size)
ht: HashTable = HashTable(mb)
ht.set_int("x", 42)
p: t.CPtr = ht["x"]
v: int = c.Deref(p)
"""
__slots__: t.CVoid | t.CPtr # Base address of slot array
__count__: t.CSizeT = 0 # Number of occupied entries
__capacity__: t.CSizeT = 16 # Total slot count (power of two)
__tombstones__: t.CSizeT = 0 # Tombstone entry count
__mbuddy__: memhub.MemBuddy | t.CPtr
__iter_index__: t.CSizeT = 0
def __new__(self, mb: memhub.MemBuddy | t.CPtr) -> t.CPtr:
# Heap-allocate HashTable object (6 fields × 8 bytes = 48 bytes)
return mb.alloc(48)
def __init__(self, mb: memhub.MemBuddy | t.CPtr):
self.__mbuddy__ = mb
self.__capacity__ = _HT_INIT_CAP
self.__count__ = 0
self.__tombstones__ = 0
self.__slots__ = mb.alloc(self.__capacity__ * _HT_SLOT_SIZE)
# Initialize all slots to empty (hash = 0)
string.memset(self.__slots__, 0, self.__capacity__ * _HT_SLOT_SIZE)
def _slot_hash(self, idx: t.CSizeT) -> t.CUInt64T:
"""Read hash value from target slot."""
ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE)
return ptr[0]
def _slot_key(self, idx: t.CSizeT) -> str:
"""Read key pointer from target slot."""
ptr: str | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE + 8)
return ptr[0]
def _slot_value(self, idx: t.CSizeT) -> t.CPtr:
"""Read value pointer from target slot."""
ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE + 16)
return ptr[0]
def _set_slot(self, idx: t.CSizeT, h: t.CUInt64T, key: str, val: t.CPtr):
"""Assign three fields of the target slot."""
h_ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE)
h_ptr[0] = h
k_ptr: str | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE + 8)
k_ptr[0] = key
v_ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE + 16)
v_ptr[0] = val
def _find_slot(self, key: str, h: t.CUInt64T) -> t.CSizeT:
"""Locate slot index for key insertion or lookup.
Returns: Index of matching key if found; otherwise returns index of first usable empty/tombstone slot.
"""
mask: t.CSizeT = self.__capacity__ - 1
idx: t.CSizeT = t.CSizeT(h) & mask
first_tombstone: t.CSizeT = self.__capacity__
i: t.CSizeT = 0
while i < self.__capacity__:
sh: t.CUInt64T = self._slot_hash(idx)
if sh == _HT_EMPTY:
if first_tombstone < self.__capacity__:
return first_tombstone
return idx
if sh == _HT_TOMBSTONE:
if first_tombstone >= self.__capacity__:
first_tombstone = idx
else:
if sh == h:
sk: str = self._slot_key(idx)
if sk == key:
return idx
idx = (idx + 1) & mask
i += 1
if first_tombstone < self.__capacity__:
return first_tombstone
return self.__capacity__
def _resize(self, new_cap: t.CSizeT):
"""Resize table and rehash all entries."""
old_slots: t.CVoid | t.CPtr = self.__slots__
old_cap: t.CSizeT = self.__capacity__
new_slots: t.CVoid | t.CPtr = self.__mbuddy__.alloc(new_cap * _HT_SLOT_SIZE)
if new_slots == None:
return
string.memset(new_slots, 0, new_cap * _HT_SLOT_SIZE)
self.__slots__ = new_slots
self.__capacity__ = new_cap
self.__count__ = 0
self.__tombstones__ = 0
i: t.CSizeT = 0
while i < old_cap:
old_h_ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(old_slots) + i * _HT_SLOT_SIZE)
h: t.CUInt64T = old_h_ptr[0]
if h != _HT_EMPTY and h != _HT_TOMBSTONE:
old_k_ptr: str | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(old_slots) + i * _HT_SLOT_SIZE + 8)
old_v_ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(old_slots) + i * _HT_SLOT_SIZE + 16)
k: str = old_k_ptr[0]
v: t.CPtr = old_v_ptr[0]
idx: t.CSizeT = self._find_slot(k, h)
self._set_slot(idx, h, k, v)
self.__count__ += 1
i += 1
def __setitem__(self, key: str, val: t.CNeedPtr):
"""Insert or update key-value pair. val automatically decays to pointer (t.CNeedPtr: no conversion if already pointer)."""
h: t.CUInt64T = _fnv1a_hash(key)
idx: t.CSizeT = self._find_slot(key, h)
if idx >= self.__capacity__:
return
sh: t.CUInt64T = self._slot_hash(idx)
if sh == _HT_EMPTY or sh == _HT_TOMBSTONE:
if sh == _HT_TOMBSTONE:
self.__tombstones__ -= 1
self._set_slot(idx, h, key, val)
self.__count__ += 1
# Resize when load factor > 0.75
load: t.CSizeT = self.__count__ + self.__tombstones__
if load * 4 >= self.__capacity__ * 3:
self._resize(self.__capacity__ * 2)
else:
self._set_slot(idx, h, key, val)
def __getitem__(self, key: str) -> t.CPtr:
"""Lookup key and return value pointer. Returns None if key not found."""
h: t.CUInt64T = _fnv1a_hash(key)
idx: t.CSizeT = self._find_slot(key, h)
if idx >= self.__capacity__:
return None
sh: t.CUInt64T = self._slot_hash(idx)
if sh == _HT_EMPTY or sh == _HT_TOMBSTONE:
return None
return self._slot_value(idx)
def __contains__(self, key: str) -> t.CInt:
"""Check whether key exists in table."""
h: t.CUInt64T = _fnv1a_hash(key)
idx: t.CSizeT = self._find_slot(key, h)
if idx >= self.__capacity__:
return 0
sh: t.CUInt64T = self._slot_hash(idx)
if sh == _HT_EMPTY or sh == _HT_TOMBSTONE:
return 0
return 1
def __delitem__(self, key: str) -> t.CInt:
"""Delete entry by key. Return 1 on success, 0 if key not present."""
h: t.CUInt64T = _fnv1a_hash(key)
idx: t.CSizeT = self._find_slot(key, h)
if idx >= self.__capacity__:
return 0
sh: t.CUInt64T = self._slot_hash(idx)
if sh == _HT_EMPTY or sh == _HT_TOMBSTONE:
return 0
h_ptr: t.CUInt64T | t.CPtr = (t.CVoid | t.CPtr)(t.CUInt64T(self.__slots__) + idx * _HT_SLOT_SIZE)
h_ptr[0] = _HT_TOMBSTONE
self.__count__ -= 1
self.__tombstones__ += 1
return 1
def __len__(self) -> t.CSizeT:
return self.__count__
def __iter__(self) -> HashTable | t.CPtr:
self.__iter_index__ = 0
return self
def __next__(self) -> str:
while self.__iter_index__ < self.__capacity__:
idx: t.CSizeT = self.__iter_index__
self.__iter_index__ = idx + 1
sh: t.CUInt64T = self._slot_hash(idx)
if sh != _HT_EMPTY and sh != _HT_TOMBSTONE:
return self._slot_key(idx)
raise StopIteration
def get(self, key: str, default: t.CPtr) -> t.CPtr:
"""Return value pointer; return default pointer if key not found."""
h: t.CUInt64T = _fnv1a_hash(key)
idx: t.CSizeT = self._find_slot(key, h)
if idx >= self.__capacity__:
return default
sh: t.CUInt64T = self._slot_hash(idx)
if sh == _HT_EMPTY or sh == _HT_TOMBSTONE:
return default
return self._slot_value(idx)
def set_int(self, key: str, val: int):
"""Store integer value. Value is copied into mbuddy-managed storage."""
storage: t.CVoid | t.CPtr = self.__mbuddy__.alloc(8)
int_ptr: int | t.CPtr = t.CVoid(t.CUInt64T(storage), t.CPtr)
int_ptr[0] = val
self[key] = storage
def set_str(self, key: str, val: str):
"""Store string value. Directly store the string pointer."""
self[key] = val