Files
TransPyC/includes/hashtable.py
2026-07-18 19:25:40 +08:00

241 lines
9.2 KiB
Python
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 - 开放寻址哈希表库
# 使用线性探测处理冲突,支持自动扩容和墓碑删除
# 键类型固定为 str (i8*),值类型为 void* (通用指针)
import t, c
import string
import memhub
from stdint import *
# 空槽标记 (hash == 0)
_HT_EMPTY: t.CDefine = 0
# 墓碑标记 (hash == 1)
_HT_TOMBSTONE: t.CDefine = 1
# 默认初始容量 (必须是 2 的幂)
_HT_INIT_CAP: t.CDefine = 16
# 槽大小: hash(8) + key(8) + value(8) = 24 字节
_HT_SLOT_SIZE: t.CDefine = 24
# FNV-1a 64-bit 常量
_FNV_OFFSET: t.CUInt64T = 0xcbf29ce484222325
_FNV_PRIME: t.CUInt64T = 0x100000001b3
def _fnv1a_hash(key: str) -> t.CUInt64T:
"""FNV-1a 64-bit 哈希函数"""
h: t.CUInt64T = _FNV_OFFSET
k: str = key
while k[0] != 0:
h = h ^ t.CUInt64T(k[0])
h = h * _FNV_PRIME
k += 1
# 保证 h >= 2 (避开空槽 0 和墓碑 1)
if h < 2:
h = 2
return h
class HashTable:
"""开放寻址哈希表,使用线性探测处理冲突。
键类型固定为 str (i8*),值类型为 void* (通用指针)。
使用 mbuddy 分配器管理内存。负载因子超过 0.75 时自动扩容。
用法:
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 # 槽位数组基址
__count__: t.CSizeT = 0 # 已用条目数
__capacity__: t.CSizeT = 16 # 槽位总数 (2 的幂)
__tombstones__: t.CSizeT = 0 # 墓碑数
__mbuddy__: memhub.MemBuddy | t.CPtr
__iter_index__: t.CSizeT = 0
def __new__(self, mb: memhub.MemBuddy | t.CPtr) -> t.CPtr:
# 堆分配 HashTable 对象 (6 字段 x 8 字节 = 48 字节)
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)
# 初始化所有槽为空 (hash = 0)
string.memset(self.__slots__, 0, self.__capacity__ * _HT_SLOT_SIZE)
def _slot_hash(self, idx: t.CSizeT) -> t.CUInt64T:
"""读取槽位的 hash 值"""
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:
"""读取槽位的 key 指针"""
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:
"""读取槽位的 value 指针"""
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):
"""设置槽位的三个字段"""
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:
"""查找 key 应该插入或已存在的槽位索引。
返回: 如果找到 key返回其索引否则返回第一个可插入的空槽/墓碑索引。
"""
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):
"""扩容并重新哈希所有条目"""
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):
"""插入或更新键值对。val 自动取地址 (t.CNeedPtr: 已是指针则不取)。"""
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
# 负载因子 > 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:
"""查找 key返回值指针。不存在返回 None。"""
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:
"""检查 key 是否存在。"""
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:
"""删除 key。成功返回 1key 不存在返回 0。"""
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:
"""返回值指针,不存在则返回 default。"""
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):
"""存储 int 值。值复制到 mbuddy 持久存储。"""
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):
"""存储 str 值。直接存储字符串指针。"""
self[key] = val