const coral = @import("./coral.zig"); const hashes = @import("./hashes.zig"); const io = @import("./io.zig"); const std = @import("std"); pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(Key)) type { const load_max = 0.75; const max_int = std.math.maxInt(usize); return struct { allocator: std.mem.Allocator, entry_map: []?Entry = &.{}, len: usize = 0, pub const Entry = struct { key: Key, value: Value, fn write_into(self: Entry, table: *Self) bool { const hash_max = @min(max_int, table.entry_map.len); var hashed_key = traits.hash(self.key) % hash_max; var iterations = @as(usize, 0); while (true) : (iterations += 1) { std.debug.assert(iterations < table.entry_map.len); const table_entry = &(table.entry_map[hashed_key] orelse { table.entry_map[hashed_key] = .{ .key = self.key, .value = self.value, }; table.len += 1; return true; }); if (traits.are_equal(table_entry.key, self.key)) { return false; } hashed_key = (hashed_key +% 1) % hash_max; } } }; pub const Entries = struct { table: *const Self, iterations: usize, pub fn next(self: *Entries) ?*Entry { while (self.iterations < self.table.entry_map.len) { defer self.iterations += 1; if (self.table.entry_map[self.iterations]) |*entry| { return entry; } } return null; } }; const Self = @This(); pub fn entries(self: *const Self) Entries { return .{ .table = self, .iterations = 0, }; } pub fn remove(self: *Self, key: Key) ?Entry { const hash_max = @min(max_int, self.entry_map.len); var hashed_key = key.hash() % hash_max; while (true) { const entry = &(self.entry_map[hashed_key] orelse continue); if (self.keys_equal(entry.key, key)) { const original_entry = entry.*; self.entry_map[hashed_key] = null; return original_entry; } hashed_key = (hashed_key +% 1) % hash_max; } } pub fn replace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!?Entry { try self.rehash(load_max); std.debug.assert(self.entry_map.len > self.len); { const hash_max = @min(max_int, self.entry_map.len); var hashed_key = traits.hash(key) % hash_max; while (true) { const entry = &(self.entry_map[hashed_key] orelse { self.entry_map[hashed_key] = .{ .key = key, .value = value, }; self.len += 1; return null; }); if (traits.are_equal(key, entry.key)) { const original_entry = entry.*; entry.* = .{ .key = key, .value = value, }; return original_entry; } hashed_key = (hashed_key +% 1) % hash_max; } } } pub fn clear(self: *Self) void { for (self.entry_map) |*entry| { entry.* = null; } self.len = 0; } pub fn deinit(self: *Self) void { if (self.entry_map.len == 0) { return; } self.allocator.free(self.entry_map); self.* = undefined; } pub fn get_ptr(self: Self, key: Key) ?*Value { if (self.len == 0) { return null; } const hash_max = @min(max_int, self.entry_map.len); var hashed_key = traits.hash(key) % hash_max; var iterations = @as(usize, 0); while (iterations < self.len) : (iterations += 1) { const entry = &(self.entry_map[hashed_key] orelse return null); if (traits.are_equal(entry.key, key)) { return &entry.value; } hashed_key = (hashed_key +% 1) % hash_max; } return null; } pub fn get(self: Self, key: Key) ?Value { if (self.get_ptr(key)) |value| { return value.*; } return null; } pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool { try self.rehash(load_max); std.debug.assert(self.entry_map.len > self.len); const entry = Entry{ .key = key, .value = value, }; return entry.write_into(self); } pub fn load_factor(self: Self) f32 { return if (self.entry_map.len == 0) 1 else @as(f32, @floatFromInt(self.len)) / @as(f32, @floatFromInt(self.entry_map.len)); } pub fn rehash(self: *Self, max_load: f32) std.mem.Allocator.Error!void { if (self.load_factor() <= max_load) { return; } var table = Self{ .allocator = self.allocator, }; errdefer table.deinit(); table.entry_map = allocate: { const min_len = @max(1, self.len); const table_size = min_len * 2; const zeroed_entry_map = try self.allocator.alloc(?Entry, table_size); errdefer self.allocator.free(zeroed_entry_map); for (zeroed_entry_map) |*entry| { entry.* = null; } break: allocate zeroed_entry_map; }; for (self.entry_map) |maybe_entry| { if (maybe_entry) |entry| { std.debug.assert(entry.write_into(&table)); } } self.deinit(); self.* = table; } }; } pub fn Traits(comptime Key: type) type { return struct { are_equal: fn (Key, Key) bool, hash: fn (Key) usize, }; } pub fn enum_traits(comptime Enum: type) Traits(Enum) { const enums = struct { fn are_equal(a: Enum, b: Enum) bool { return a == b; } fn hash(value: Enum) usize { return @intFromEnum(value) % std.math.maxInt(usize); } }; return .{ .are_equal = enums.are_equal, .hash = enums.hash, }; } pub const string_traits = init: { const strings = struct { fn hash(value: []const u8) usize { return hashes.djb2(@typeInfo(usize).Int, value); } }; break: init Traits([]const u8){ .are_equal = coral.io.are_equal, .hash = strings.hash, }; }; pub const usize_traits = init: { const usizes = struct { fn are_equal(a: usize, b: usize) bool { return a == b; } fn hash(value: usize) usize { return value; } }; break: init Traits(usize){ .are_equal = usizes.are_equal, .hash = usizes.hash, }; };