275 lines
5.9 KiB
Zig
275 lines
5.9 KiB
Zig
const debug = @import("./debug.zig");
|
|
|
|
const io = @import("./io.zig");
|
|
|
|
const list = @import("./list.zig");
|
|
|
|
const math = @import("./math.zig");
|
|
|
|
pub fn StringTable(comptime Value: type) type {
|
|
return Table([]const io.Byte, Value, struct {
|
|
const Self = @This();
|
|
|
|
fn equals(key_a: []const io.Byte, key_b: []const io.Byte) bool {
|
|
return io.are_equal(key_a, key_b);
|
|
}
|
|
|
|
fn hash(key: []const io.Byte) usize {
|
|
return io.djb2_hash(@typeInfo(usize).Int, key);
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type {
|
|
const load_max = 0.75;
|
|
const max_int = math.max_int(@typeInfo(usize).Int);
|
|
|
|
return struct {
|
|
allocator: io.Allocator,
|
|
traits: Traits,
|
|
count: usize,
|
|
entries: []?Entry,
|
|
|
|
pub const Entry = struct {
|
|
key: Key,
|
|
value: Value,
|
|
|
|
fn write_into(self: Entry, table: *Self) bool {
|
|
const hash_max = @min(max_int, table.entries.len);
|
|
var hashed_key = table.hash_key(self.key) % hash_max;
|
|
var iterations = @as(usize, 0);
|
|
|
|
while (true) : (iterations += 1) {
|
|
debug.assert(iterations < table.entries.len);
|
|
|
|
const table_entry = &(table.entries[hashed_key] orelse {
|
|
table.entries[hashed_key] = .{
|
|
.key = self.key,
|
|
.value = self.value,
|
|
};
|
|
|
|
table.count += 1;
|
|
|
|
return true;
|
|
});
|
|
|
|
if (table.keys_equal(table_entry.key, self.key)) {
|
|
return false;
|
|
}
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Iterable = struct {
|
|
table: *Self,
|
|
iterations: usize,
|
|
|
|
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();
|
|
|
|
pub fn as_iterable(self: *Self) Iterable {
|
|
return .{
|
|
.table = self,
|
|
.iterations = 0,
|
|
};
|
|
}
|
|
|
|
fn hash_key(self: Self, key: Key) usize {
|
|
return if (@sizeOf(Traits) == 0) Traits.hash(key) else self.traits.hash(key);
|
|
}
|
|
|
|
pub fn remove(self: *Self, key: Key) ?Entry {
|
|
const hash_max = @min(max_int, self.entries.len);
|
|
var hashed_key = self.hash_key(key) % hash_max;
|
|
|
|
while (true) {
|
|
const entry = &(self.entries[hashed_key] orelse continue);
|
|
|
|
if (self.keys_equal(entry.key, key)) {
|
|
const original_entry = entry.*;
|
|
|
|
self.entries[hashed_key] = null;
|
|
|
|
return original_entry;
|
|
}
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
}
|
|
}
|
|
|
|
pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry {
|
|
try self.rehash(load_max);
|
|
|
|
debug.assert(self.entries.len > self.count);
|
|
|
|
{
|
|
const hash_max = @min(max_int, self.entries.len);
|
|
const has_context = @sizeOf(Traits) != 0;
|
|
var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
|
|
|
|
while (true) {
|
|
const entry = &(self.entries[hashed_key] orelse {
|
|
self.entries[hashed_key] = .{
|
|
.key = key,
|
|
.value = value,
|
|
};
|
|
|
|
self.count += 1;
|
|
|
|
return null;
|
|
});
|
|
|
|
if (has_context) {
|
|
if (self.traits.equals(entry.key, key)) {
|
|
const original_entry = entry.*;
|
|
|
|
entry.* = .{
|
|
.key = key,
|
|
.value = value,
|
|
};
|
|
|
|
return original_entry;
|
|
}
|
|
} else {
|
|
if (Traits.equals(entry.key, key)) {
|
|
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,
|
|
};
|
|
|
|
return entry.write_into(self);
|
|
}
|
|
|
|
fn keys_equal(self: Self, key_a: Key, key_b: Key) bool {
|
|
if (@sizeOf(Traits) == 0) {
|
|
return Traits.equals(key_a, key_b);
|
|
} else {
|
|
return self.traits.equals(key_a, key_b);
|
|
}
|
|
}
|
|
|
|
pub fn lookup(self: Self, key: Key) ?Value {
|
|
if (self.count == 0) {
|
|
return null;
|
|
}
|
|
|
|
const hash_max = @min(max_int, self.entries.len);
|
|
const has_context = @sizeOf(Traits) != 0;
|
|
var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
|
|
var iterations = @as(usize, 0);
|
|
|
|
while (iterations < self.count) : (iterations += 1) {
|
|
const entry = &(self.entries[hashed_key] orelse return null);
|
|
|
|
if (has_context) {
|
|
if (self.traits.equals(entry.key, key)) {
|
|
return entry.value;
|
|
}
|
|
} else {
|
|
if (Traits.equals(entry.key, key)) {
|
|
return entry.value;
|
|
}
|
|
}
|
|
|
|
hashed_key = (hashed_key +% 1) % hash_max;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn make(allocator: io.Allocator, traits: Traits) Self {
|
|
return .{
|
|
.allocator = allocator,
|
|
.count = 0,
|
|
.entries = &.{},
|
|
.traits = traits,
|
|
};
|
|
}
|
|
|
|
pub fn rehash(self: *Self, max_load: f32) io.AllocationError!void {
|
|
if (self.calculate_load_factor() <= max_load) {
|
|
return;
|
|
}
|
|
|
|
var table = make(self.allocator, self.traits);
|
|
|
|
errdefer table.free();
|
|
|
|
table.entries = allocate: {
|
|
const min_count = @max(1, self.count);
|
|
const table_size = min_count * 2;
|
|
|
|
break: allocate try io.allocate_many(self.allocator, table_size, @as(?Entry, null));
|
|
};
|
|
|
|
for (self.entries) |maybe_entry| {
|
|
if (maybe_entry) |entry| {
|
|
debug.assert(entry.write_into(&table));
|
|
}
|
|
}
|
|
|
|
self.free();
|
|
|
|
self.* = table;
|
|
}
|
|
};
|
|
}
|