stage2 更多底层函数 stage1 layout正确使用 内置函数重新编写
This commit is contained in:
1 parent
fb0ff9b663
commit
4141e7d574
15 files changed
+2588
-122
No files matched your search
@@ -0,0 +1,448 @@
|
||||
// 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 .{ .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 hash_usize(key: *u8): usize {
|
||||
var value = (key as *usize).*;
|
||||
ret value * 2654435761;
|
||||
}
|
||||
|
||||
fn eq_usize(a: *u8, b: *u8): bool {
|
||||
ret (a as *usize).* == (b as *usize).*;
|
||||
}
|
||||
|
||||
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 hash_view(key: *u8): usize {
|
||||
var view = (key as *ByteView).*;
|
||||
var h: usize = 14695981039346656037;
|
||||
var i: usize = 0;
|
||||
while i < view.len {
|
||||
h = (h ^ (view.ptr[i] as usize)) * 1099511628211;
|
||||
i = i + 1;
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn eq_view(a: *u8, b: *u8): bool {
|
||||
ret ByteView.eq((a as *ByteView).*, (b as *ByteView).*);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
Reference in new issue
Block a user