| ... | ... | @@ -0,0 +1,262 @@ |
| 1 | const assert = @import("index.zig").assert; |
| 2 | const math = @import("math.zig"); |
| 3 | const mem = @import("mem.zig"); |
| 4 | const Allocator = mem.Allocator; |
| 5 | |
| 6 | const want_modification_safety = !@compile_var("is_release"); |
| 7 | const debug_u32 = if (want_modification_safety) void else u32; |
| 8 | |
| 9 | pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)->bool) { |
| 10 | entries: []Entry, |
| 11 | size: isize, |
| 12 | max_distance_from_start_index: isize, |
| 13 | allocator: &Allocator, |
| 14 | // this is used to detect bugs where a hashtable is edited while an iterator is running. |
| 15 | modification_count: debug_u32, |
| 16 | |
| 17 | const Self = HashMap(K, V, hash, eql); |
| 18 | |
| 19 | pub struct Entry { |
| 20 | used: bool, |
| 21 | distance_from_start_index: isize, |
| 22 | key: K, |
| 23 | value: V, |
| 24 | } |
| 25 | |
| 26 | pub struct Iterator { |
| 27 | hm: &Self, |
| 28 | // how many items have we returned |
| 29 | count: isize, |
| 30 | // iterator through the entry array |
| 31 | index: isize, |
| 32 | // used to detect concurrent modification |
| 33 | initial_modification_count: debug_u32, |
| 34 | |
| 35 | pub fn next(it: &Iterator) -> ?&Entry { |
| 36 | if (want_modification_safety) { |
| 37 | assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification |
| 38 | } |
| 39 | if (it.count >= it.hm.size) return null; |
| 40 | while (it.index < it.hm.entries.len; it.index += 1) { |
| 41 | const entry = &it.hm.entries[it.index]; |
| 42 | if (entry.used) { |
| 43 | it.index += 1; |
| 44 | it.count += 1; |
| 45 | return entry; |
| 46 | } |
| 47 | } |
| 48 | unreachable{} // no next item |
| 49 | } |
| 50 | }; |
| 51 | |
| 52 | pub fn init(hm: &Self, allocator: &Allocator, capacity: isize) { |
| 53 | assert(capacity > 0); |
| 54 | hm.allocator = allocator; |
| 55 | hm.init_capacity(capacity); |
| 56 | } |
| 57 | |
| 58 | pub fn deinit(hm: &Self) { |
| 59 | free(_entries); |
| 60 | } |
| 61 | |
| 62 | pub fn clear(hm: &Self) { |
| 63 | for (hm.entries) |*entry| { |
| 64 | entry.used = false; |
| 65 | } |
| 66 | hm.size = 0; |
| 67 | hm.max_distance_from_start_index = 0; |
| 68 | hm.increment_modification_count(); |
| 69 | } |
| 70 | |
| 71 | pub fn put(hm: &Self, key: K, value: V) { |
| 72 | hm.increment_modification_count(); |
| 73 | hm.internal_put(key, value); |
| 74 | |
| 75 | // if we get too full (60%), double the capacity |
| 76 | if (hm.size * 5 >= hm.entries.len * 3) { |
| 77 | const old_entries = hm.entries; |
| 78 | hm.init_capacity(hm.entries.len * 2); |
| 79 | // dump all of the old elements into the new table |
| 80 | for (old_entries) |*old_entry| { |
| 81 | if (old_entry.used) { |
| 82 | hm.internal_put(old_entry.key, old_entry.value); |
| 83 | } |
| 84 | } |
| 85 | hm.allocator.free(hm.allocator, ([]u8)(old_entries)); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | pub fn get(hm: &Self, key: K) { |
| 90 | return internal_get(key); |
| 91 | } |
| 92 | |
| 93 | pub fn remove(hm: &Self, key: K) { |
| 94 | hm.increment_modification_count(); |
| 95 | const start_index = hm.key_to_index(key); |
| 96 | {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) { |
| 97 | const index = (start_index + roll_over) % hm.entries.len; |
| 98 | const entry = &hm.entries[index]; |
| 99 | |
| 100 | assert(entry.used); // key not found |
| 101 | |
| 102 | if (!eql(entry.key, key)) continue; |
| 103 | |
| 104 | while (roll_over < hm.entries.len; roll_over += 1) { |
| 105 | const next_index = (start_index + roll_over + 1) % hm.entries.len; |
| 106 | const next_entry = &hm.entries[next_index]; |
| 107 | if (!next_entry.used || next_entry.distance_from_start_index == 0) { |
| 108 | entry.used = false; |
| 109 | hm.size -= 1; |
| 110 | return; |
| 111 | } |
| 112 | *entry = *next_entry; |
| 113 | entry.distance_from_start_index -= 1; |
| 114 | entry = next_entry; |
| 115 | } |
| 116 | unreachable{} // shifting everything in the table |
| 117 | }} |
| 118 | unreachable{} // key not found |
| 119 | } |
| 120 | |
| 121 | pub fn entry_iterator(hm: &Self) -> Iterator { |
| 122 | return Iterator { |
| 123 | .hm = hm, |
| 124 | .count = 0, |
| 125 | .index = 0, |
| 126 | .initial_modification_count = hm.modification_count, |
| 127 | }; |
| 128 | } |
| 129 | |
| 130 | fn init_capacity(hm: &Self, capacity: isize) { |
| 131 | hm.capacity = capacity; |
| 132 | hm.entries = ([]Entry)(hm.allocator.alloc(hm.allocator, capacity * @sizeof(Entry))); |
| 133 | hm.size = 0; |
| 134 | hm.max_distance_from_start_index = 0; |
| 135 | for (hm.entries) |*entry| { |
| 136 | entry.used = false; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | fn increment_modification_count(hm: &Self) { |
| 141 | if (want_modification_safety) { |
| 142 | hm.modification_count += 1; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | fn internal_put(hm: &Self, K orig_key, V orig_value) { |
| 147 | var key = orig_key; |
| 148 | var value = orig_value; |
| 149 | const start_index = key_to_index(key); |
| 150 | var roll_over: isize = 0; |
| 151 | var distance_from_start_index: isize = 0; |
| 152 | while (roll_over < hm.entries.len; {roll_over += 1; distance_from_start_index += 1}) { |
| 153 | const index = (start_index + roll_over) % hm.entries.len; |
| 154 | const entry = &hm.entries[index]; |
| 155 | |
| 156 | if (entry.used && !eql(entry.key, key)) { |
| 157 | if (entry.distance_from_start_index < distance_from_start_index) { |
| 158 | // robin hood to the rescue |
| 159 | const tmp = *entry; |
| 160 | hm.max_distance_from_start_index = math.max(isize)( |
| 161 | hm.max_distance_from_start_index, distance_from_start_index); |
| 162 | *entry = Entry { |
| 163 | .used = true, |
| 164 | .distance_from_start_index = distance_from_start_index, |
| 165 | .key = key, |
| 166 | .value = value, |
| 167 | }; |
| 168 | key = tmp.key; |
| 169 | value = tmp.value; |
| 170 | distance_from_start_index = tmp.distance_from_start_index; |
| 171 | } |
| 172 | continue; |
| 173 | } |
| 174 | |
| 175 | if (!entry.used) { |
| 176 | // adding an entry. otherwise overwriting old value with |
| 177 | // same key |
| 178 | hm.size += 1; |
| 179 | } |
| 180 | |
| 181 | hm.max_distance_from_start_index = math.max(isize)(distance_from_start_index, hm.max_distance_from_start_index); |
| 182 | *entry = { |
| 183 | .used = true, |
| 184 | .distance_from_start_index = distance_from_start_index, |
| 185 | .key = key, |
| 186 | .value = value, |
| 187 | }; |
| 188 | return; |
| 189 | } |
| 190 | unreachable{} // put into a full map |
| 191 | } |
| 192 | |
| 193 | fn internal_get(hm: &Self, key: K) -> ?&Entry { |
| 194 | const start_index = key_to_index(key); |
| 195 | {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) { |
| 196 | const index = (start_index + roll_over) % hm.entries.len; |
| 197 | const entry = &hm.entries[index]; |
| 198 | |
| 199 | if (!entry.used) return null; |
| 200 | if (eql(entry.key, key)) return entry; |
| 201 | }} |
| 202 | return null; |
| 203 | } |
| 204 | |
| 205 | Entry *internal_get(const K &key) const { |
| 206 | int start_index = key_to_index(key); |
| 207 | for (int roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { |
| 208 | int index = (start_index + roll_over) % _capacity; |
| 209 | Entry *entry = &_entries[index]; |
| 210 | |
| 211 | if (!entry->used) |
| 212 | return NULL; |
| 213 | |
| 214 | if (EqualFn(entry->key, key)) |
| 215 | return entry; |
| 216 | } |
| 217 | return NULL; |
| 218 | } |
| 219 | |
| 220 | fn key_to_index(hm: &Self, key: K) -> isize { |
| 221 | return isize(hash(key)) % hm.entries.len; |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | var global_allocator = Allocator { |
| 226 | .alloc = global_alloc, |
| 227 | .realloc = global_realloc, |
| 228 | .free = global_free, |
| 229 | .context = null, |
| 230 | }; |
| 231 | |
| 232 | var some_mem: [200]u8 = undefined; |
| 233 | var some_mem_index: isize = 0; |
| 234 | |
| 235 | fn global_alloc(self: &Allocator, n: isize) -> %[]u8 { |
| 236 | const result = some_mem[some_mem_index ... some_mem_index + n]; |
| 237 | some_mem_index += n; |
| 238 | return result; |
| 239 | } |
| 240 | |
| 241 | fn global_realloc(self: &Allocator, old_mem: []u8, new_size: isize) -> %[]u8 { |
| 242 | const result = %return global_alloc(self, new_size); |
| 243 | @memcpy(result.ptr, old_mem.ptr, old_mem.len); |
| 244 | return result; |
| 245 | } |
| 246 | |
| 247 | fn global_free(self: &Allocator, old_mem: []u8) { |
| 248 | } |
| 249 | |
| 250 | #attribute("test") |
| 251 | fn basic_hash_map_test() { |
| 252 | var map: HashMap(i32, i32, hash_i32, eql_i32); |
| 253 | map.init(&global_allocator, 4); |
| 254 | defer map.deinit(); |
| 255 | } |
| 256 | |
| 257 | fn hash_i32(x: i32) -> u32 { |
| 258 | *(&u32)(&x) |
| 259 | } |
| 260 | fn eql_i32(a: i32, b: i32) -> bool { |
| 261 | a == b |
| 262 | } |