stage2 更多的工具库
This commit is contained in:
1 parent
b3e38ba7e4
commit
c30143609a
8 files changed
+2711
-1138
No files matched your search
@@ -0,0 +1,235 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 开放寻址集合。键按字节复制到集合自己的存储中;集合只释放自身存储,
|
||||
// 不释放键值内部引用的资源。哈希与相等回调的约定和 Map 一致。
|
||||
type .{ .Status } = @import("error.spl");
|
||||
type .{ .Map, .LookupResult } = @import("map.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
|
||||
|
||||
type Set = @self;
|
||||
|
||||
map: Map,
|
||||
|
||||
fn new(key_size: usize, key_align: usize,
|
||||
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Set {
|
||||
ret Set {
|
||||
.map = Map.new(key_size, key_align, 1, 1, hash_fn, eq_fn),
|
||||
};
|
||||
}
|
||||
|
||||
fn valid(self: Set): bool {
|
||||
ret Map.valid(self.map) && self.map.val_size == 1;
|
||||
}
|
||||
|
||||
fn size(self: Set): usize {
|
||||
ret self.map.size;
|
||||
}
|
||||
|
||||
fn capacity(self: Set): usize {
|
||||
ret self.map.states.cap;
|
||||
}
|
||||
|
||||
fn reserve(self: *Set, need: usize): Status {
|
||||
ret Map.reserve(&self.map, need);
|
||||
}
|
||||
|
||||
// 重复插入保持集合大小不变。
|
||||
fn insert(self: *Set, key: *u8): Status {
|
||||
var marker: u8 = 1;
|
||||
ret Map.put(&self.map, key, &marker);
|
||||
}
|
||||
|
||||
fn find(self: *Set, key: *u8): LookupResult {
|
||||
var marker: u8 = 0;
|
||||
ret Map.get(&self.map, key, &marker);
|
||||
}
|
||||
|
||||
fn remove(self: *Set, key: *u8): LookupResult {
|
||||
ret Map.del(&self.map, key);
|
||||
}
|
||||
|
||||
// 遍历 [0, slot_count);仅 slot_occupied 为真时 key_at 有效。
|
||||
fn slot_count(self: Set): usize {
|
||||
ret Map.slot_count(self.map);
|
||||
}
|
||||
|
||||
fn slot_occupied(self: Set, index: usize): bool {
|
||||
ret Map.slot_occupied(Map.slot_state(self.map, index));
|
||||
}
|
||||
|
||||
fn key_at(self: *Set, index: usize): *u8 {
|
||||
ret Map.key_at(&self.map, index);
|
||||
}
|
||||
|
||||
fn clear(self: *Set) {
|
||||
Map.clear(&self.map);
|
||||
}
|
||||
|
||||
fn take(source: *Set): Set {
|
||||
ret Set { .map = Map.take(&source.map) };
|
||||
}
|
||||
|
||||
fn drop(self: *Set) {
|
||||
Map.drop(&self.map);
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected Set error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn found(set: *Set, key: *u8): bool {
|
||||
match find(set, key) {
|
||||
.Found => { ret true; },
|
||||
.NotFound => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn removed(set: *Set, key: *u8): bool {
|
||||
match remove(set, key) {
|
||||
.Found => { ret true; },
|
||||
.NotFound => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn constant_hash(key: *u8): usize {
|
||||
ret 3;
|
||||
}
|
||||
|
||||
fn test_insert_find_remove() {
|
||||
var set = new(@sizeof(usize), @alignof(usize), hash_usize, eq_usize);
|
||||
defer { drop(&set); }
|
||||
@assert(valid(set));
|
||||
@assert(size(set) == 0 && capacity(set) == 0);
|
||||
|
||||
var i: usize = 0;
|
||||
while i < 1000 {
|
||||
expect_ok(insert(&set, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(set) == 1000, "all unique keys inserted");
|
||||
|
||||
i = 0;
|
||||
while i < 1000 {
|
||||
@assert(found(&set, &i as *u8), "key survives growth");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
// 重复键不增加 size。
|
||||
i = 0;
|
||||
while i < 1000 {
|
||||
expect_ok(insert(&set, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(set) == 1000, "duplicate insertion is idempotent");
|
||||
|
||||
i = 0;
|
||||
while i < 1000 {
|
||||
if i % 2 == 0 { @assert(removed(&set, &i as *u8)); }
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(set) == 500);
|
||||
|
||||
i = 0;
|
||||
while i < 1000 {
|
||||
@assert(found(&set, &i as *u8) == (i % 2 != 0), "removed membership");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < 1000 {
|
||||
if i % 2 == 0 { expect_ok(insert(&set, &i as *u8)); }
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(set) == 1000, "removed keys can be reinserted");
|
||||
|
||||
var occupied: usize = 0;
|
||||
i = 0;
|
||||
while i < slot_count(set) {
|
||||
if slot_occupied(set, i) {
|
||||
var key = (key_at(&set, i) as *usize).*;
|
||||
@assert(key < 1000, "iteration yields stored key");
|
||||
occupied = occupied + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(occupied == size(set), "iteration visits each member once");
|
||||
|
||||
var missing: usize = 2000;
|
||||
@assert(!removed(&set, &missing as *u8));
|
||||
clear(&set);
|
||||
@assert(size(set) == 0 && capacity(set) != 0, "clear retains allocation");
|
||||
}
|
||||
|
||||
fn test_collisions_and_take() {
|
||||
var set = new(@sizeof(usize), @alignof(usize), constant_hash, eq_usize);
|
||||
defer { drop(&set); }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < 64 {
|
||||
expect_ok(insert(&set, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
i = 0;
|
||||
while i < 64 {
|
||||
@assert(found(&set, &i as *u8), "collision chain lookup");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
// 删除整条碰撞链后重新插入,验证墓碑会被复用且探测会终止。
|
||||
i = 0;
|
||||
while i < 64 {
|
||||
@assert(removed(&set, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(set) == 0);
|
||||
i = 64;
|
||||
while i < 128 {
|
||||
expect_ok(insert(&set, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
i = 64;
|
||||
while i < 128 {
|
||||
@assert(found(&set, &i as *u8), "tombstone collision chain reuse");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var moved = take(&set);
|
||||
defer { drop(&moved); }
|
||||
@assert(size(set) == 0 && capacity(set) == 0, "take clears source");
|
||||
@assert(size(moved) == 64, "take preserves members");
|
||||
i = 64;
|
||||
while i < 128 {
|
||||
@assert(found(&moved, &i as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_byte_view_keys() {
|
||||
var set = new(@sizeof(ByteView), @alignof(ByteView), hash_view, eq_view);
|
||||
defer { drop(&set); }
|
||||
|
||||
var alpha = ByteView.from_slice("alpha");
|
||||
var alpha_copy = ByteView.from_slice("alpha");
|
||||
var beta = ByteView.from_slice("beta");
|
||||
expect_ok(insert(&set, &alpha as *u8));
|
||||
expect_ok(insert(&set, &alpha_copy as *u8));
|
||||
expect_ok(insert(&set, &beta as *u8));
|
||||
@assert(size(set) == 2, "ByteView keys compare by content");
|
||||
@assert(found(&set, &alpha_copy as *u8));
|
||||
@assert(removed(&set, &alpha_copy as *u8));
|
||||
@assert(!found(&set, &alpha as *u8));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_insert_find_remove();
|
||||
test_collisions_and_take();
|
||||
test_byte_view_keys();
|
||||
}
|
||||
Reference in new issue
Block a user