2023-07-10 02:10:56 +02:00
|
|
|
const debug = @import("./debug.zig");
|
|
|
|
|
|
|
|
const io = @import("./io.zig");
|
|
|
|
|
|
|
|
const list = @import("./list.zig");
|
|
|
|
|
|
|
|
const math = @import("./math.zig");
|
|
|
|
|
|
|
|
pub fn Slab(comptime Value: type) type {
|
|
|
|
return struct {
|
|
|
|
next_index: usize,
|
|
|
|
entries: EntryList,
|
|
|
|
|
|
|
|
const EntryList = list.Stack(union (enum) {
|
|
|
|
value: Value,
|
|
|
|
next_index: usize,
|
|
|
|
});
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
pub fn lookup(self: Self, key: usize) ?Value {
|
|
|
|
if (key == 0 or key > self.entries.values.len) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return switch (self.entries.values[key - 1]) {
|
|
|
|
.value => |value| value,
|
|
|
|
.next_index => null,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn free(self: *Self) void {
|
|
|
|
self.entries.free();
|
|
|
|
|
|
|
|
self.next_index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn insert(self: *Self, value: Value) io.AllocationError!usize {
|
|
|
|
if (self.next_index < self.entries.values.len) {
|
|
|
|
const index = self.next_index;
|
|
|
|
const entry = &self.entries.values[index];
|
|
|
|
|
|
|
|
debug.assert(entry.* == .next_index);
|
|
|
|
|
|
|
|
self.next_index = entry.next_index;
|
|
|
|
entry.* = .{.value = value};
|
|
|
|
|
|
|
|
return index + 1;
|
|
|
|
} else {
|
|
|
|
try self.entries.push_one(.{.value = value});
|
|
|
|
|
|
|
|
self.next_index += 1;
|
|
|
|
|
|
|
|
return self.next_index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 00:52:56 +02:00
|
|
|
pub fn insert_at(self: *Self, key: usize, value: Value) bool {
|
|
|
|
if (self.entries.values.len < key) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const entry = &self.entries.values[key - 1];
|
|
|
|
|
|
|
|
if (entry.* == .value) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.next_index = entry.next_index;
|
|
|
|
entry.* = .{.value = value};
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-07-10 02:10:56 +02:00
|
|
|
pub fn make(allocator: io.Allocator) Self {
|
|
|
|
return .{
|
|
|
|
.next_index = 0,
|
|
|
|
.entries = EntryList.make(allocator),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove(self: *Self, key: usize) ?Value {
|
|
|
|
if (key == 0 or key > self.entries.values.len) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const index = key - 1;
|
|
|
|
const entry = &self.entries.values[index];
|
|
|
|
|
|
|
|
return switch (entry.*) {
|
|
|
|
.next_index => null,
|
|
|
|
|
|
|
|
.value => get_value: {
|
|
|
|
const value = entry.value;
|
|
|
|
|
|
|
|
entry.* = .{.next_index = self.next_index};
|
|
|
|
self.next_index = index;
|
|
|
|
|
|
|
|
break: get_value value;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-22 00:03:25 +02:00
|
|
|
pub fn StringTable(comptime Value: type) type {
|
2023-07-29 17:04:27 +02:00
|
|
|
return Table([]const io.Byte, Value, struct {
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
fn equals(_: Self, key_a: []const io.Byte, key_b: []const io.Byte) bool {
|
|
|
|
return io.equals(key_a, key_b);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hash(_: Self, key: []const io.Byte) usize {
|
|
|
|
return io.djb2_hash(@typeInfo(usize).Int, key);
|
|
|
|
}
|
2023-07-22 00:03:25 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type {
|
2023-07-10 02:10:56 +02:00
|
|
|
const load_max = 0.75;
|
2023-07-22 14:57:39 +02:00
|
|
|
const hash_int = @typeInfo(usize).Int;
|
|
|
|
const max_int = math.max_int(hash_int);
|
|
|
|
const min_int = math.min_int(hash_int);
|
2023-07-10 02:10:56 +02:00
|
|
|
|
|
|
|
return struct {
|
|
|
|
allocator: io.Allocator,
|
2023-07-29 17:04:27 +02:00
|
|
|
traits: Traits,
|
2023-07-10 02:10:56 +02:00
|
|
|
count: usize,
|
|
|
|
entries: []?Entry,
|
|
|
|
|
|
|
|
pub const Entry = struct {
|
|
|
|
key: Key,
|
|
|
|
value: Value,
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
fn write_into(self: Entry, table: *Self) bool {
|
|
|
|
const hash_max = @min(max_int, table.entries.len);
|
|
|
|
var hashed_key = math.wrap(table.traits.hash(self.key), min_int, hash_max);
|
2023-07-10 02:10:56 +02:00
|
|
|
var iterations = @as(usize, 0);
|
|
|
|
|
|
|
|
while (true) : (iterations += 1) {
|
2023-07-29 17:04:27 +02:00
|
|
|
debug.assert(iterations < table.entries.len);
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
const table_entry = &(table.entries[hashed_key] orelse {
|
|
|
|
table.entries[hashed_key] = .{
|
2023-07-10 02:10:56 +02:00
|
|
|
.key = self.key,
|
|
|
|
.value = self.value,
|
|
|
|
};
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
table.count += 1;
|
|
|
|
|
2023-07-10 02:10:56 +02:00
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
if (table.traits.equals(table_entry.key, self.key)) {
|
2023-07-10 02:10:56 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const Iterable = struct {
|
|
|
|
table: *Self,
|
2023-07-22 00:03:25 +02:00
|
|
|
iterations: usize,
|
2023-07-10 02:10:56 +02:00
|
|
|
|
|
|
|
pub fn next(self: *Iterable) ?Entry {
|
|
|
|
while (self.iterations < self.table.entries.len) {
|
|
|
|
defer self.iterations += 1;
|
|
|
|
|
|
|
|
if (self.table.entries[self.iterations]) |entry| {
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
2023-07-22 00:03:25 +02:00
|
|
|
pub fn as_iterable(self: *Self) Iterable {
|
|
|
|
return .{
|
|
|
|
.table = self,
|
|
|
|
.iterations = 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-19 00:52:56 +02:00
|
|
|
pub fn remove(self: *Self, key: Key) ?Entry {
|
2023-07-22 14:57:39 +02:00
|
|
|
const hash_max = @min(max_int, self.entries.len);
|
2023-07-29 17:04:27 +02:00
|
|
|
var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
|
2023-07-19 00:52:56 +02:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const entry = &(self.entries[hashed_key] orelse continue);
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
if (self.traits.equals(entry.key, key)) {
|
2023-07-19 00:52:56 +02:00
|
|
|
const original_entry = entry.*;
|
|
|
|
|
|
|
|
self.entries[hashed_key] = null;
|
|
|
|
|
|
|
|
return original_entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 02:10:56 +02:00
|
|
|
pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry {
|
|
|
|
try self.rehash(load_max);
|
|
|
|
|
|
|
|
debug.assert(self.entries.len > self.count);
|
|
|
|
|
|
|
|
{
|
2023-07-22 14:57:39 +02:00
|
|
|
const hash_max = @min(max_int, self.entries.len);
|
2023-07-29 17:04:27 +02:00
|
|
|
var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
|
2023-07-10 02:10:56 +02:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const entry = &(self.entries[hashed_key] orelse {
|
|
|
|
self.entries[hashed_key] = .{
|
|
|
|
.key = key,
|
|
|
|
.value = value,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.count += 1;
|
|
|
|
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
if (self.traits.equals(entry.key, key)) {
|
2023-07-10 02:10:56 +02:00
|
|
|
const original_entry = entry.*;
|
|
|
|
|
|
|
|
entry.* = .{
|
|
|
|
.key = key,
|
|
|
|
.value = value,
|
|
|
|
};
|
|
|
|
|
|
|
|
return original_entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn calculate_load_factor(self: Self) f32 {
|
|
|
|
return if (self.entries.len == 0) 1 else @as(f32, @floatFromInt(self.count)) / @as(f32, @floatFromInt(self.entries.len));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear(self: *Self) void {
|
|
|
|
for (self.entries) |*entry| {
|
|
|
|
entry.* = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn free(self: *Self) void {
|
|
|
|
if (self.entries.len == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.allocator.deallocate(self.entries);
|
|
|
|
|
|
|
|
self.entries = &.{};
|
|
|
|
self.count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn insert(self: *Self, key: Key, value: Value) io.AllocationError!bool {
|
|
|
|
try self.rehash(load_max);
|
|
|
|
|
|
|
|
debug.assert(self.entries.len > self.count);
|
|
|
|
|
|
|
|
const entry = Entry{
|
|
|
|
.key = key,
|
|
|
|
.value = value,
|
|
|
|
};
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
return entry.write_into(self);
|
2023-07-10 02:10:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn lookup(self: Self, key: Key) ?Value {
|
|
|
|
if (self.count == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-07-22 14:57:39 +02:00
|
|
|
const hash_max = @min(max_int, self.entries.len);
|
2023-07-29 17:04:27 +02:00
|
|
|
var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
|
2023-07-10 02:10:56 +02:00
|
|
|
var iterations = @as(usize, 0);
|
|
|
|
|
|
|
|
while (iterations < self.count) : (iterations += 1) {
|
|
|
|
const entry = &(self.entries[hashed_key] orelse return null);
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
if (self.traits.equals(entry.key, key)) {
|
2023-07-10 02:10:56 +02:00
|
|
|
return entry.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
pub fn make(allocator: io.Allocator, traits: Traits) Self {
|
2023-07-10 02:10:56 +02:00
|
|
|
return .{
|
|
|
|
.allocator = allocator,
|
|
|
|
.count = 0,
|
|
|
|
.entries = &.{},
|
2023-07-29 17:04:27 +02:00
|
|
|
.traits = traits,
|
2023-07-10 02:10:56 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rehash(self: *Self, max_load: f32) io.AllocationError!void {
|
|
|
|
if (self.calculate_load_factor() <= max_load) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
var table = make(self.allocator, self.traits);
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
errdefer table.free();
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
table.entries = allocate: {
|
|
|
|
const min_count = @max(1, self.count);
|
|
|
|
const table_size = min_count * 2;
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
break: allocate try io.allocate_many(self.allocator, table_size, @as(?Entry, null));
|
|
|
|
};
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
for (self.entries) |maybe_entry| {
|
|
|
|
if (maybe_entry) |entry| {
|
|
|
|
debug.assert(entry.write_into(&table));
|
2023-07-10 02:10:56 +02:00
|
|
|
}
|
2023-07-29 17:04:27 +02:00
|
|
|
}
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
self.free();
|
2023-07-10 02:10:56 +02:00
|
|
|
|
2023-07-29 17:04:27 +02:00
|
|
|
self.* = table;
|
|
|
|
}
|
2023-07-10 02:10:56 +02:00
|
|
|
};
|
|
|
|
}
|