305 lines
6.4 KiB
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,
|
|
};
|