jc/hash/table.jai

191 lines
4.7 KiB
Text

#module_parameters(RUN_TESTS := false);
// Dead simple key-value pair type (aka. hash table or hash map)
Table :: struct(Key: Type, Value: Type) {
allocator: Allocator;
slots: [..]Slot;
free_slots: [..]int;
count: int;
Slot :: struct {
hash: u32 = invalid_hash;
key: Key = ---;
value: Value = ---;
}
hash_proc :: hash.murmur32;
invalid_hash :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
number_of_items_to_allocate_initially :: 16; // @note(judah): must be a power of two
}
get :: (t: *Table, key: t.Key) -> t.Value, bool {
slot, ok := find_slot(t, get_hash(t, key));
if !ok {
return mem.zero_of(t.Value), false;
}
return slot.value, true;
}
set :: (t: *Table, key: t.Key, value: t.Value) {
hash := get_hash(t, key);
slot, exists := find_slot(t, hash);
if !exists {
slot = create_or_reuse_slot(t);
slot.hash = hash;
}
slot.key = key;
slot.value = value;
}
exists :: (t: *Table, key: t.Key) -> bool {
_, exists := find_slot(t, get_hash(t, key));
return exists;
}
// @note(judah): we use 'delete' instead of 'remove' because it's a keyword...
delete :: (t: *Table, key: t.Key) -> t.Value, bool {
slot, ok, idx := find_slot(t, get_hash(t, key));
if !ok return mem.zero_of(t.Value), false;
last_value := slot.value;
mark_slot_for_reuse(t, idx);
return last_value, true;
}
reset :: (t: *Table) {
t.count = 0;
t.slots.count = 0;
t.free_slots.count = 0;
}
for_expansion :: (t: *Table, body: Code, flags: For_Flags) #expand {
#assert (flags & .POINTER == 0) "cannot iterate by pointer";
for <=(flags & .REVERSE == .REVERSE) slot: t.slots if slot.hash != t.invalid_hash {
`it := slot.value;
`it_index := slot.key;
#insert,scope(body)(break = break slot) body;
}
}
#scope_file;
get_hash :: inline (t: *Table, key: t.Key) -> u32 {
hash := t.hash_proc(key);
basic.assert(hash != t.invalid_hash, "key % collided with invalid hash marker (%)", key, t.invalid_hash);
return hash;
}
find_slot :: (t: *Table, hash: u32) -> *t.Slot, bool, int {
for * t.slots if it.hash == hash {
return it, true, it_index;
}
return null, false, -1;
}
create_or_reuse_slot :: (t: *Table) -> *t.Slot {
inline try_lazy_init(t);
if t.free_slots.count > 0 {
slot_idx := t.free_slots[t.free_slots.count - 1];
t.free_slots.count -= 1;
return *t.slots[slot_idx];
}
if t.slots.allocated == 0 {
array.resize(*t.slots, t.number_of_items_to_allocate_initially);
}
else if t.slots.count >= t.slots.allocated {
array.resize(*t.slots, mem.next_power_of_two(t.slots.allocated));
}
slot := array.append(*t.slots);
t.count = t.slots.count;
return slot;
}
mark_slot_for_reuse :: (t: *Table, index: int) {
inline try_lazy_init(t);
t.count -= 1;
t.slots[index] = .{ hash = t.invalid_hash };
array.append(*t.free_slots, index);
}
try_lazy_init :: inline (t: *Table) {
mem.lazy_set_allocator(t);
mem.lazy_set_allocator(*t.slots);
mem.lazy_set_allocator(*t.free_slots);
}
mem :: #import "jc/memory";
array :: #import "jc/array";
hash :: #import "jc/hash";
basic :: #import "Basic"; // @future
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
test.run("basic operations", t => {
ITERATIONS :: 64;
values: Table(int, int);
for 0..ITERATIONS {
set(*values, it, it * it);
}
for 0..ITERATIONS {
v, ok := get(*values, it);
test.expect(t, v == it * it);
}
for 0..ITERATIONS if it % 2 == 0 {
_, ok := delete(*values, it);
test.expect(t, ok);
}
for 0..ITERATIONS if it % 2 == 0 {
_, ok := get(*values, it);
test.expect(t, !ok);
}
});
test.run("free slots", t => {
values: Table(int, int);
set(*values, 1, 100);
set(*values, 2, 200);
set(*values, 3, 300);
test.expect(t, values.count == 3);
test.expect(t, values.slots.allocated == values.number_of_items_to_allocate_initially);
// deleting something that doesn't exist should do nothing
_, ok := delete(*values, 0);
test.expect(t, !ok);
test.expect(t, values.count == 3);
delete(*values, 2);
test.expect(t, values.count == 2);
});
test.run("iteration", t => {
values: Table(int, int);
for 0..10 set(*values, it, it * it);
test.expect(t, values.count == 11);
for v, k: values test.expect(t, v == k * k);
for < v, k: values test.expect(t, v == k * k);
});
}