Files
spl/stage2/utils/map.spl
T
2026-09-17 22:32:36 +08:00

426 lines
12 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.
// Expect retcode = 0
// 开放寻址 + tombstone 哈希表。键和值都按字节复制到表自己的存储。
// 键类型由 hash_fn / eq_fn 解释:回调收到指向键字节的指针(如 *usize / *ByteView)。
// 普通赋值只复制句柄(与 Vec 一样),所有权转移必须使用 take。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
type .{ .copy } = @import("../sys/mem.spl");
// 槽位状态:0=EMPTY,1=OCCUPIED,2=DELETED(墓碑,仅在扩容重哈希时清除)。
// 负载因子 70/128。
type Map = @self;
keys: Vec,
vals: Vec,
states: Vec,
size: usize,
key_size: usize,
val_size: usize,
hash_fn: fn(*u8): usize,
eq_fn: fn(*u8, *u8): bool,
fn blank(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Map {
ret Map {
.keys = Vec.blank(key_size, key_align),
.vals = Vec.blank(val_size, val_align),
.states = Vec.blank(1, 1),
.size = 0,
.key_size = key_size,
.val_size = val_size,
.hash_fn = hash_fn,
.eq_fn = eq_fn,
};
}
fn new(key_size: usize, key_align: usize, val_size: usize, val_align: usize,
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Map {
ret blank(key_size, key_align, val_size, val_align, hash_fn, eq_fn);
}
fn valid(self: Map): bool {
if self.key_size == 0 || self.val_size == 0 { ret false; }
if self.states.len != self.keys.len || self.states.len != self.vals.len {
ret false;
}
if self.size > self.states.len { ret false; }
ret true;
}
fn size(self: Map): usize {
ret self.size;
}
fn capacity(self: Map): usize {
ret self.states.cap;
}
// 迭代支持:遍历 [0, slot_count),slot_state 为 1 时 key_at/val_at 有效。
fn slot_count(self: Map): usize {
ret self.states.len;
}
fn slot_state(self: Map, index: usize): u8 {
ret self.states.data[index];
}
fn slot_occupied(state: u8): bool {
ret state == 1;
}
fn key_at(self: *Map, index: usize): *u8 {
ret self.keys.data + index * self.key_size;
}
fn val_at(self: *Map, index: usize): *u8 {
ret self.vals.data + index * self.val_size;
}
// 返回占用键的槽位;未找到返回 ~0。容量为 0 时直接未找到。
fn find_slot(self: *Map, key: *u8): usize {
if self.states.cap == 0 { ret ~(0 as usize); }
var mask = self.states.cap - 1;
var i = self.hash_fn(key) & mask;
while self.states.data[i] != 0 {
if self.states.data[i] == 1 && self.eq_fn(self.keys.data + i * self.key_size, key) {
ret i;
}
i = (i + 1) & mask;
}
ret ~(0 as usize);
}
// 要求容量非零且键不存在,直接写入第一个空槽(重哈希专用)。
fn place(self: *Map, key: *u8, val: *u8) {
var mask = self.states.cap - 1;
var i = self.hash_fn(key) & mask;
while self.states.data[i] != 0 {
i = (i + 1) & mask;
}
copy((self.keys.data + i * self.key_size) as *any, key as *any, self.key_size);
copy((self.vals.data + i * self.val_size) as *any, val as *any, self.val_size);
self.states.data[i] = 1;
self.size = self.size + 1;
}
// 保证至少 need 个槽位。所有检查在旧表改动前完成,失败保持原表不变。
fn reserve(self: *Map, need: usize): Status {
if !valid(self.*) { ret .Err(make(.InvalidArgument)); }
if need <= self.states.cap { ret .Ok; }
var new_cap = self.states.cap;
if new_cap < 8 { new_cap = 8; }
while new_cap < need {
if new_cap > (~(0 as usize)) / 2 {
new_cap = need;
break;
}
new_cap = new_cap * 2;
}
var fresh = self.*;
fresh.keys = Vec.blank(self.key_size, self.keys.elem_align);
fresh.vals = Vec.blank(self.val_size, self.vals.elem_align);
fresh.states = Vec.blank(1, 1);
fresh.size = 0;
match Vec.reserve(&fresh.keys, new_cap) {
.Ok => {},
.Err(error) => { drop(&fresh); ret .Err(error); },
}
match Vec.reserve(&fresh.vals, new_cap) {
.Ok => {},
.Err(error) => { drop(&fresh); ret .Err(error); },
}
match Vec.reserve(&fresh.states, new_cap) {
.Ok => {},
.Err(error) => { drop(&fresh); ret .Err(error); },
}
fresh.keys.len = new_cap;
fresh.vals.len = new_cap;
fresh.states.len = new_cap;
var i: usize = 0;
while i < new_cap {
fresh.states.data[i] = 0;
i = i + 1;
}
i = 0;
while i < self.states.len {
if self.states.data[i] == 1 {
place(&fresh, self.keys.data + i * self.key_size,
self.vals.data + i * self.val_size);
}
i = i + 1;
}
var old_keys = self.keys;
var old_vals = self.vals;
var old_states = self.states;
self.* = fresh;
Vec.drop(&old_keys);
Vec.drop(&old_vals);
Vec.drop(&old_states);
ret .Ok;
}
// 覆盖已存在的键;否则插入并可能扩容。
fn put(self: *Map, key: *u8, val: *u8): Status {
if !valid(self.*) || key == null || val == null {
ret .Err(make(.InvalidArgument));
}
var existing = find_slot(self, key);
if existing != ~(0 as usize) {
copy((self.vals.data + existing * self.val_size) as *any, val as *any, self.val_size);
ret .Ok;
}
if self.states.cap == 0 || (self.size + 1) * 128 / self.states.cap >= 70 {
var need = self.states.cap * 2;
if self.states.cap == 0 { need = 8; }
match reserve(self, need) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
place(self, key, val);
ret .Ok;
}
// 找到键则把值复制到 out 并返回 true;否则 out 不变并返回 false。
fn get(self: *Map, key: *u8, out: *u8): bool {
if !valid(self.*) || key == null || out == null { ret false; }
var i = find_slot(self, key);
if i == ~(0 as usize) { ret false; }
copy(out as *any, (self.vals.data + i * self.val_size) as *any, self.val_size);
ret true;
}
fn del(self: *Map, key: *u8): bool {
if !valid(self.*) || key == null { ret false; }
var i = find_slot(self, key);
if i == ~(0 as usize) { ret false; }
self.states.data[i] = 2;
self.size = self.size - 1;
ret true;
}
// 清空内容但保留容量。墓碑与负载计数一并复位。
fn clear(self: *Map) {
var i: usize = 0;
while i < self.states.len {
self.states.data[i] = 0;
i = i + 1;
}
self.size = 0;
}
fn drop(self: *Map) {
Vec.drop(&self.keys);
Vec.drop(&self.vals);
Vec.drop(&self.states);
self.size = 0;
}
fn take(source: *Map): Map {
var result = source.*;
source.keys = Vec.blank(source.key_size, source.keys.elem_align);
source.vals = Vec.blank(source.val_size, source.vals.elem_align);
source.states = Vec.blank(1, 1);
source.size = 0;
ret result;
}
// ---- 测试辅助 ----
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected Map error"); },
}
}
fn expect_invalid(result: Status) {
match result {
.Ok => { @assert(false, "expected Map error"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {},
_ => { @assert(false, "unexpected Map error kind"); },
}
},
}
}
fn count_occupied(m: *Map): usize {
var n: usize = 0;
var i: usize = 0;
while i < slot_count(m.*) {
if slot_occupied(slot_state(m.*, i)) { n = n + 1; }
i = i + 1;
}
ret n;
}
fn make_int_map(): Map {
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_usize, eq_usize);
}
fn test_put_get_update() {
var m = make_int_map();
@assert(size(m) == 0 && capacity(m) == 0, "empty map");
@assert(valid(m));
var key: usize = 0;
var value: i32 = 0;
var out: i32 = 0;
expect_invalid(put(&m, null, &value as *u8));
expect_invalid(put(&m, &key as *u8, null));
@assert(size(m) == 0, "failed put preserves map");
key = 7;
value = 70;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 1 && valid(m));
out = -1;
@assert(get(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
value = 71;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 1, "update does not grow size");
out = -1;
@assert(get(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites");
key = 8;
out = 123;
@assert(!get(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
drop(&m);
@assert(size(m) == 0 && capacity(m) == 0, "drop resets map");
}
fn test_growth_and_delete() {
var m = make_int_map();
var i: i32 = 0;
while i < 1000 {
var key: usize = i as usize;
var value: i32 = i * 3 - 7;
expect_ok(put(&m, &key as *u8, &value as *u8));
i = i + 1;
}
@assert(size(m) == 1000);
@assert(count_occupied(&m) == 1000, "all slots accounted");
i = 0;
while i < 1000 {
var key: usize = i as usize;
var out: i32 = 0;
@assert(get(&m, &key as *u8, &out as *u8), "get after growth");
@assert(out == i * 3 - 7, "value after growth");
i = i + 1;
}
i = 0;
while i < 500 {
var key: usize = i as usize;
@assert(del(&m, &key as *u8));
i = i + 1;
}
@assert(size(m) == 500 && count_occupied(&m) == 500, "deleted slots not occupied");
i = 0;
while i < 500 {
var key: usize = i as usize;
var out: i32 = 0;
@assert(!get(&m, &key as *u8, &out as *u8), "deleted key missing");
i = i + 1;
}
// 墓碑槽位可被再次插入复用,且不会重复计数。
var key: usize = 12345;
var value: i32 = 99;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 501);
var out: i32 = 0;
@assert(get(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone");
@assert(del(&m, &key as *u8), "delete key");
@assert(size(m) == 500);
@assert(!get(&m, &key as *u8, &out as *u8));
clear(&m);
@assert(size(m) == 0 && count_occupied(&m) == 0, "clear empties");
drop(&m);
}
fn test_take_and_self_put() {
var m = make_int_map();
var key: usize = 5;
var value: i32 = 55;
expect_ok(put(&m, &key as *u8, &value as *u8));
var moved = take(&m);
@assert(size(m) == 0 && capacity(m) == 0, "take leaves source empty");
var out: i32 = 0;
@assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content");
// 值指针可以指向表内已有的值(按字节移动,允许重叠)。
var alias: i32 = 0;
@assert(get(&moved, &key as *u8, &alias as *u8));
expect_ok(put(&moved, &key as *u8, &alias as *u8));
@assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "self value put");
drop(&moved);
}
fn test_byte_view_keys() {
var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32),
hash_view, eq_view);
var alpha = ByteView.from_slice("alpha");
var beta = ByteView.from_slice("beta");
var gamma = ByteView.from_slice("gamma");
var one: i32 = 1;
var two: i32 = 2;
var three: i32 = 3;
expect_ok(put(&m, &alpha as *u8, &one as *u8));
expect_ok(put(&m, &beta as *u8, &two as *u8));
expect_ok(put(&m, &gamma as *u8, &three as *u8));
@assert(size(m) == 3);
var out: i32 = 0;
@assert(get(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha");
@assert(get(&m, &beta as *u8, &out as *u8) && out == 2, "beta");
@assert(get(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma");
// 内容相同的不同 ByteView 视为同一个键。
var alpha2 = ByteView.from_slice("alpha");
out = 0;
@assert(get(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key");
@assert(size(m) == 3, "equal content does not insert");
@assert(del(&m, &beta as *u8), "delete beta");
@assert(size(m) == 2);
@assert(!get(&m, &beta as *u8, &out as *u8));
drop(&m);
}
fn main() {
test_put_get_update();
test_growth_and_delete();
test_take_and_self_put();
test_byte_view_keys();
}