241 lines
9.2 KiB
Python
241 lines
9.2 KiB
Python
# 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。成功返回 1,key 不存在返回 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
|