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 deinit(self: *Self) void { if (self.entries.len == 0) { return; } self.allocator.deallocate(self.entries); self.entries = &.{}; self.count = 0; } pub fn init(allocator: io.Allocator, traits: Traits) Self { return .{ .allocator = allocator, .count = 0, .entries = &.{}, .traits = traits, }; } 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 rehash(self: *Self, max_load: f32) io.AllocationError!void { if (self.calculate_load_factor() <= max_load) { return; } var table = init(self.allocator, self.traits); errdefer table.deinit(); 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.deinit(); self.* = table; } }; }