181 lines
4.1 KiB
Text
181 lines
4.1 KiB
Text
// Dead simple key-value pair type (aka. hash table or hash map)
|
|
Record :: struct(Key: Type, Value: Type) {
|
|
allocator: Allocator;
|
|
slots: [..]Slot;
|
|
free_slots: [..]int;
|
|
count: int;
|
|
|
|
Slot :: struct {
|
|
hash: u32 = InvalidHash;
|
|
key: Key = ---;
|
|
value: Value = ---;
|
|
}
|
|
|
|
HashProc :: Murmur32;
|
|
InvalidHash :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
|
|
AllocatedItemsAtStart :: 16;
|
|
}
|
|
|
|
// @note(judah): Not sure if I like these names, but I want to give them a try
|
|
Fetch :: Get;
|
|
Update :: Set;
|
|
Exists :: Has;
|
|
|
|
Get :: (r: *Record, key: r.Key) -> r.Value, bool {
|
|
slot, ok := FindSlot(r, GetHash(r, key));
|
|
if !ok
|
|
{ return zero_of(r.Value), false; }
|
|
|
|
return slot.value, true;
|
|
}
|
|
|
|
Set :: (r: *Record, key: r.Key, value: r.Value) {
|
|
hash := GetHash(r, key);
|
|
slot, exists := FindSlot(r, hash);
|
|
if !exists {
|
|
slot = CreateOrReuseSlot(r);
|
|
slot.hash = hash;
|
|
}
|
|
|
|
slot.key = key;
|
|
slot.value = value;
|
|
}
|
|
|
|
Has :: (r: *Record, key: r.Key) -> bool {
|
|
_, exists := FindSlot(r, GetHash(r, key));
|
|
return exists;
|
|
}
|
|
|
|
Remove :: (r: *Record, key: r.Key) -> bool, r.Value {
|
|
slot, ok, idx := FindSlot(r, GetHash(r, key));
|
|
if !ok
|
|
{ return false, zero_of(r.Value); }
|
|
|
|
last_value := slot.value;
|
|
MarkSlotForReuse(r, idx);
|
|
|
|
return true, last_value;
|
|
}
|
|
|
|
Reset :: (t: *Record) {
|
|
t.count = 0;
|
|
t.slots.count = 0;
|
|
t.free_slots.count = 0;
|
|
}
|
|
|
|
for_expansion :: (r: *Record, body: Code, flags: For_Flags) #expand {
|
|
#assert (flags & .POINTER == 0) "cannot iterate by pointer";
|
|
for <=(flags & .REVERSE == .REVERSE) slot: r.slots if slot.hash != r.InvalidHash {
|
|
`it := slot.value;
|
|
`it_index := slot.key;
|
|
#insert,scope(body)(break = break slot) body;
|
|
}
|
|
}
|
|
|
|
|
|
#scope_file;
|
|
|
|
GetHash :: inline (r: *Record, key: r.Key) -> u32 {
|
|
hash := r.HashProc(key);
|
|
Assert(hash != r.InvalidHash, "key collided with invalid hash");
|
|
return hash;
|
|
}
|
|
|
|
FindSlot :: (r: *Record, hash: u32) -> *r.Slot, bool, int {
|
|
for * r.slots if it.hash == hash {
|
|
return it, true, it_index;
|
|
}
|
|
|
|
return null, false, -1;
|
|
}
|
|
|
|
CreateOrReuseSlot :: (r: *Record) -> *r.Slot {
|
|
inline LazyInit(r);
|
|
|
|
if r.free_slots.count > 0 {
|
|
slot_idx := r.free_slots[r.free_slots.count - 1];
|
|
r.free_slots.count -= 1;
|
|
return *r.slots[slot_idx];
|
|
}
|
|
|
|
if r.slots.allocated == 0 {
|
|
Resize(*r.slots, r.AllocatedItemsAtStart);
|
|
}
|
|
else if r.slots.count >= r.slots.allocated {
|
|
Resize(*r.slots, NextPowerOfTwo(r.slots.allocated));
|
|
}
|
|
|
|
slot := Append(*r.slots);
|
|
r.count = r.slots.count;
|
|
return slot;
|
|
}
|
|
|
|
MarkSlotForReuse :: (t: *Record, index: int) {
|
|
inline LazyInit(t);
|
|
|
|
t.count -= 1;
|
|
t.slots[index] = .{ hash = t.InvalidHash };
|
|
|
|
Append(*t.free_slots, index);
|
|
}
|
|
|
|
LazyInit :: inline (t: *Record) {
|
|
TrySetAllocator(t);
|
|
TrySetAllocator(*t.slots);
|
|
TrySetAllocator(*t.free_slots);
|
|
}
|
|
|
|
|
|
#if RunTests #run {
|
|
Test("kv:basic operations", t => {
|
|
ITERATIONS :: 64;
|
|
|
|
values: Record(int, int);
|
|
for 0..ITERATIONS {
|
|
Set(*values, it, it * it);
|
|
}
|
|
|
|
for 0..ITERATIONS {
|
|
v, ok := Get(*values, it);
|
|
Expect(v == it * it);
|
|
}
|
|
|
|
for 0..ITERATIONS if it % 2 == 0 {
|
|
ok := Remove(*values, it);
|
|
Expect(ok);
|
|
}
|
|
|
|
for 0..ITERATIONS if it % 2 == 0 {
|
|
_, ok := Get(*values, it);
|
|
Expect(!ok);
|
|
}
|
|
});
|
|
|
|
Test("kv:free slots", t => {
|
|
values: Record(int, int);
|
|
|
|
Set(*values, 1, 100);
|
|
Set(*values, 2, 200);
|
|
Set(*values, 3, 300);
|
|
Expect(values.count == 3);
|
|
Expect(values.slots.allocated == values.AllocatedItemsAtStart);
|
|
|
|
// deleting something that doesn't exist should do nothing
|
|
ok := Remove(*values, 0);
|
|
Expect(!ok);
|
|
Expect(values.count == 3);
|
|
|
|
Remove(*values, 2);
|
|
Expect(values.count == 2);
|
|
});
|
|
|
|
Test("kv:iteration", t => {
|
|
values: Record(int, int);
|
|
|
|
for 0..10 Set(*values, it, it * it);
|
|
Expect(values.count == 11);
|
|
|
|
for v, k: values Expect(v == k * k);
|
|
for < v, k: values Expect(v == k * k);
|
|
});
|
|
}
|