236 lines
5.8 KiB
Plaintext
236 lines
5.8 KiB
Plaintext
// Expect retcode = 0
|
|
|
|
// 开放寻址集合。键按字节复制到集合自己的存储中;集合只释放自身存储,
|
|
// 不释放键值内部引用的资源。哈希与相等回调的约定和 Map 一致。
|
|
type .{ .Status } = @import("error.spl");
|
|
type .{ .Map, .LookupResult, .hash_kind_usize, .hash_kind_view, .hash_kind_constant } =
|
|
@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_kind: usize): Set {
|
|
ret Set {
|
|
.map = Map.new(key_size, key_align, 1, 1, hash_kind),
|
|
};
|
|
}
|
|
|
|
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_kind_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), hash_kind_constant());
|
|
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_kind_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();
|
|
}
|