2024-05-29 20:27:02 +02:00
|
|
|
const coral = @import("./coral.zig");
|
|
|
|
|
2024-06-05 00:51:00 +02:00
|
|
|
const hashes = @import("./hashes.zig");
|
2024-05-29 20:27:02 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-18 00:32:10 +02:00
|
|
|
pub fn get(self: Self, key: Key) ?*Value {
|
2024-05-29 20:27:02 +02:00
|
|
|
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 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 {
|
2024-06-05 00:51:00 +02:00
|
|
|
return hashes.djb2(@typeInfo(usize).Int, value);
|
2024-05-29 20:27:02 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
};
|