# 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