// Dead simple key-value pair type (aka. hash table or hash map) Map :: 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; } MapGet :: (r: *Map, 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; } MapSet :: (r: *Map, 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; } MapHas :: (r: *Map, key: r.Key) -> bool { _, exists := FindSlot(r, GetHash(r, key)); return exists; } MapRemove :: (r: *Map, 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; } MapReset :: (t: *Map) { t.count = 0; t.slots.count = 0; t.free_slots.count = 0; } for_expansion :: (r: *Map, 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: *Map, key: r.Key) -> u32 { hash := r.HashProc(key); Assert(hash != r.InvalidHash, "key collided with invalid hash"); return hash; } FindSlot :: (r: *Map, hash: u32) -> *r.Slot, bool, int { for * r.slots if it.hash == hash { return it, true, it_index; } return null, false, -1; } CreateOrReuseSlot :: (r: *Map) -> *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 { ArrayGrow(*r.slots, r.AllocatedItemsAtStart); } else if r.slots.count >= r.slots.allocated { ArrayGrow(*r.slots, NextPowerOfTwo(r.slots.allocated)); } slot := ArrayAppend(*r.slots); r.count = r.slots.count; return slot; } MarkSlotForReuse :: (t: *Map, index: int) { inline LazyInit(t); t.count -= 1; t.slots[index] = .{ hash = t.InvalidHash }; ArrayAppend(*t.free_slots, index); } LazyInit :: inline (t: *Map) { TrySetAllocator(t); TrySetAllocator(*t.slots); TrySetAllocator(*t.free_slots); } #if RunTests #run { Test("map:basic operations", t => { ITERATIONS :: 64; values: Map(int, int); for 0..ITERATIONS { MapSet(*values, it, it * it); } for 0..ITERATIONS { v, ok := MapGet(*values, it); Expect(v == it * it); } for 0..ITERATIONS if it % 2 == 0 { ok := MapRemove(*values, it); Expect(ok); } for 0..ITERATIONS if it % 2 == 0 { _, ok := MapGet(*values, it); Expect(!ok); } }); Test("map:free slots", t => { values: Map(int, int); MapSet(*values, 1, 100); MapSet(*values, 2, 200); MapSet(*values, 3, 300); Expect(values.count == 3); Expect(values.slots.allocated == values.AllocatedItemsAtStart); // deleting something that doesn't exist should do nothing ok := MapRemove(*values, 0); Expect(!ok); Expect(values.count == 3); MapRemove(*values, 2); Expect(values.count == 2); }); Test("map:iteration", t => { values: Map(int, int); for 0..10 MapSet(*values, it, it * it); Expect(values.count == 11); for v, k: values Expect(v == k * k); for < v, k: values Expect(v == k * k); }); }