ona/source/coral/map.zig

305 lines
6.4 KiB
Zig

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;
}
}
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;
},
};
}
};
}
pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type {
const load_max = 0.75;
return struct {
allocator: io.Allocator,
count: usize,
entries: []?Entry,
pub const Entry = struct {
key: Key,
value: Value,
fn write_into(self: Entry, entry_table: []?Entry) bool {
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), entry_table.len);
var hashed_key = math.wrap(traits.hash(self.key), math.min_int(@typeInfo(usize).Int), hash_max);
var iterations = @as(usize, 0);
while (true) : (iterations += 1) {
debug.assert(iterations < entry_table.len);
const table_entry = &(entry_table[hashed_key] orelse {
entry_table[hashed_key] = .{
.key = self.key,
.value = self.value,
};
return true;
});
if (traits.match(table_entry.key, self.key)) {
return false;
}
hashed_key = (hashed_key +% 1) % hash_max;
}
}
};
pub const Iterable = struct {
table: *Self,
iterations: usize = 0,
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 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 = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len);
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), 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 (traits.match(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);
defer self.count += 1;
const entry = Entry{
.key = key,
.value = value,
};
return entry.write_into(self.entries);
}
pub fn lookup(self: Self, key: Key) ?Value {
if (self.count == 0) {
return null;
}
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len);
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max);
var iterations = @as(usize, 0);
while (iterations < self.count) : (iterations += 1) {
const entry = &(self.entries[hashed_key] orelse return null);
if (traits.match(entry.key, key)) {
return entry.value;
}
hashed_key = (hashed_key +% 1) % hash_max;
}
return null;
}
pub fn make(allocator: io.Allocator) Self {
return .{
.allocator = allocator,
.count = 0,
.entries = &.{},
};
}
pub fn rehash(self: *Self, max_load: f32) io.AllocationError!void {
if (self.calculate_load_factor() <= max_load) {
return;
}
const min_count = math.max(1, self.count);
const table_size = min_count * 2;
const allocation = @as([*]?Entry, @ptrCast(@alignCast(try self.allocator.reallocate(null, @sizeOf(?Entry) * table_size))))[0 .. table_size];
errdefer self.allocator.deallocate(allocation);
self.entries = replace_table: {
for (allocation) |*entry| {
entry.* = null;
}
if (self.entries.len != 0) {
for (self.entries) |maybe_entry| {
if (maybe_entry) |entry| {
debug.assert(entry.write_into(allocation));
}
}
self.allocator.deallocate(self.entries);
}
break: replace_table allocation;
};
}
};
}
pub fn TableTraits(comptime Key: type) type {
return struct {
hash: fn (key: Key) usize,
match: fn (key: Key, key: Key) bool,
};
}
pub const string_table_traits = TableTraits([]const io.Byte){
.hash = struct {
fn hash(key: []const io.Byte) usize {
var hash_code = @as(usize, 5381);
for (key) |byte| {
hash_code = ((hash_code << 5) + hash_code) + byte;
}
return hash_code;
}
}.hash,
.match = io.equals,
};