// 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(); }