src/hash_map.c

Fri, 12 Aug 2022 16:48:59 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 12 Aug 2022 16:48:59 +0200
changeset 574
d566bd3e6af8
parent 573
3f3a0d19db58
child 575
b05935945637
permissions
-rw-r--r--

invert if-condition in preparation for the next bugfix

universe@549 1 /*
universe@549 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
universe@549 3 *
universe@549 4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
universe@549 5 *
universe@549 6 * Redistribution and use in source and binary forms, with or without
universe@549 7 * modification, are permitted provided that the following conditions are met:
universe@549 8 *
universe@549 9 * 1. Redistributions of source code must retain the above copyright
universe@549 10 * notice, this list of conditions and the following disclaimer.
universe@549 11 *
universe@549 12 * 2. Redistributions in binary form must reproduce the above copyright
universe@549 13 * notice, this list of conditions and the following disclaimer in the
universe@549 14 * documentation and/or other materials provided with the distribution.
universe@549 15 *
universe@549 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
universe@549 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
universe@549 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
universe@549 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
universe@549 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
universe@549 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
universe@549 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
universe@549 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
universe@549 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
universe@549 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
universe@549 26 * POSSIBILITY OF SUCH DAMAGE.
universe@549 27 */
universe@549 28
universe@549 29 #include <string.h>
universe@549 30 #include "cx/hash_map.h"
universe@550 31 #include "cx/utils.h"
universe@549 32
universe@550 33 static void cx_hash_map_clear(struct cx_map_s *map) {
universe@550 34 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 35 cx_for_n(i, hash_map->bucket_count) {
universe@550 36 struct cx_hash_map_element_s *elem = hash_map->buckets[i];
universe@550 37 if (elem != NULL) {
universe@550 38 do {
universe@550 39 struct cx_hash_map_element_s *next = elem->next;
universe@550 40 // free the key data
universe@563 41 cxFree(map->allocator, elem->key.data.obj);
universe@550 42 // free the node
universe@550 43 cxFree(map->allocator, elem);
universe@550 44 // proceed
universe@550 45 elem = next;
universe@550 46 } while (elem != NULL);
universe@550 47
universe@550 48 // do not leave a dangling pointer
universe@550 49 hash_map->buckets[i] = NULL;
universe@550 50 }
universe@550 51 }
universe@550 52 map->size = 0;
universe@550 53 }
universe@550 54
universe@550 55 static void cx_hash_map_destructor(struct cx_map_s *map) {
universe@550 56 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 57
universe@550 58 // free the buckets
universe@550 59 cx_hash_map_clear(map);
universe@550 60 cxFree(map->allocator, hash_map->buckets);
universe@550 61
universe@550 62 // free the map structure
universe@550 63 cxFree(map->allocator, map);
universe@550 64 }
universe@550 65
universe@550 66 static int cx_hash_map_put(
universe@550 67 CxMap *map,
universe@563 68 CxHashKey key,
universe@550 69 void *value
universe@550 70 ) {
universe@550 71 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 72 CxAllocator *allocator = map->allocator;
universe@550 73
universe@563 74 unsigned hash = key.hash;
universe@563 75 if (hash == 0) {
universe@563 76 cx_hash_murmur(&key);
universe@563 77 hash = key.hash;
universe@563 78 }
universe@550 79
universe@550 80 size_t slot = hash % hash_map->bucket_count;
universe@550 81 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 82 struct cx_hash_map_element_s *prev = NULL;
universe@550 83
universe@550 84 while (elm != NULL && elm->key.hash < hash) {
universe@550 85 prev = elm;
universe@550 86 elm = elm->next;
universe@550 87 }
universe@550 88
universe@574 89 if (elm != NULL && elm->key.hash == hash) {
universe@574 90 // overwrite existing element
universe@574 91 elm->data = value;
universe@574 92 } else {
universe@574 93 // insert new element
universe@574 94
universe@550 95 struct cx_hash_map_element_s *e = cxMalloc(allocator, sizeof(struct cx_hash_map_element_s));
universe@550 96 if (e == NULL) {
universe@550 97 return -1;
universe@550 98 }
universe@550 99
universe@550 100 // write the value
universe@550 101 // TODO: depending on future map features, we may want to copy here
universe@550 102 e->data = value;
universe@550 103
universe@550 104 // copy the key
universe@550 105 void *kd = cxMalloc(allocator, key.len);
universe@550 106 if (kd == NULL) {
universe@550 107 return -1;
universe@550 108 }
universe@563 109 memcpy(kd, key.data.obj, key.len);
universe@563 110 e->key.data.obj = kd;
universe@550 111 e->key.len = key.len;
universe@550 112 e->key.hash = hash;
universe@550 113
universe@550 114 // insert the element into the linked list
universe@550 115 if (prev == NULL) {
universe@550 116 hash_map->buckets[slot] = e;
universe@550 117 } else {
universe@550 118 prev->next = e;
universe@550 119 }
universe@550 120 e->next = elm;
universe@550 121
universe@550 122 // increase the size
universe@550 123 map->size++;
universe@550 124 }
universe@550 125
universe@550 126 return 0;
universe@550 127 }
universe@549 128
universe@551 129 static void cx_hash_map_unlink(
universe@551 130 struct cx_hash_map_s *hash_map,
universe@551 131 size_t slot,
universe@551 132 struct cx_hash_map_element_s *prev,
universe@551 133 struct cx_hash_map_element_s *elm
universe@551 134 ) {
universe@551 135 // unlink
universe@551 136 if (prev == NULL) {
universe@551 137 hash_map->buckets[slot] = elm->next;
universe@551 138 } else {
universe@551 139 prev->next = elm->next;
universe@551 140 }
universe@551 141 // free element
universe@563 142 cxFree(hash_map->base.allocator, elm->key.data.obj);
universe@551 143 cxFree(hash_map->base.allocator, elm);
universe@551 144 // decrease size
universe@551 145 hash_map->base.size--;
universe@551 146 }
universe@551 147
universe@549 148 /**
universe@550 149 * Helper function to avoid code duplication.
universe@549 150 *
universe@550 151 * @param map the map
universe@550 152 * @param key the key to look up
universe@550 153 * @param remove flag indicating whether the looked up entry shall be removed
universe@550 154 * @return the value corresponding to the key or \c NULL
universe@549 155 */
universe@550 156 static void *cx_hash_map_get_remove(
universe@550 157 CxMap *map,
universe@563 158 CxHashKey key,
universe@550 159 bool remove
universe@550 160 ) {
universe@550 161 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 162
universe@563 163 unsigned hash = key.hash;
universe@563 164 if (hash == 0) {
universe@563 165 cx_hash_murmur(&key);
universe@563 166 hash = key.hash;
universe@563 167 }
universe@550 168
universe@550 169 size_t slot = hash % hash_map->bucket_count;
universe@550 170 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 171 struct cx_hash_map_element_s *prev = NULL;
universe@550 172 while (elm && elm->key.hash <= hash) {
universe@550 173 if (elm->key.hash == hash && elm->key.len == key.len) {
universe@563 174 if (memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) {
universe@550 175 void *data = elm->data;
universe@550 176 if (remove) {
universe@551 177 cx_hash_map_unlink(hash_map, slot, prev, elm);
universe@550 178 }
universe@550 179 return data;
universe@550 180 }
universe@550 181 }
universe@550 182 prev = elm;
universe@550 183 elm = prev->next;
universe@550 184 }
universe@550 185
universe@550 186 return NULL;
universe@549 187 }
universe@550 188
universe@550 189 static void *cx_hash_map_get(
universe@550 190 CxMap const *map,
universe@563 191 CxHashKey key
universe@550 192 ) {
universe@550 193 // we can safely cast, because we know when remove=false, the map stays untouched
universe@550 194 return cx_hash_map_get_remove((CxMap *) map, key, false);
universe@550 195 }
universe@550 196
universe@550 197 static void *cx_hash_map_remove(
universe@550 198 CxMap *map,
universe@563 199 CxHashKey key
universe@550 200 ) {
universe@550 201 return cx_hash_map_get_remove(map, key, true);
universe@550 202 }
universe@550 203
universe@551 204 static void *cx_hash_map_iter_current_entry(CxIterator const *iter) {
universe@551 205 // struct has to have a compatible signature
universe@573 206 return (struct cx_map_entry_s *) &(iter->kv_data);
universe@551 207 }
universe@551 208
universe@551 209 static void *cx_hash_map_iter_current_key(CxIterator const *iter) {
universe@551 210 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 211 return &elm->key;
universe@551 212 }
universe@551 213
universe@551 214 static void *cx_hash_map_iter_current_value(CxIterator const *iter) {
universe@551 215 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 216 // TODO: return a pointer to data if this map is storing copies
universe@551 217 return elm->data;
universe@551 218 }
universe@551 219
universe@551 220 static bool cx_hash_map_iter_valid(CxIterator const *iter) {
universe@551 221 return iter->elem_handle != NULL;
universe@551 222 }
universe@551 223
universe@551 224 static void cx_hash_map_iter_next(CxIterator *iter) {
universe@551 225 struct cx_hash_map_s *map = iter->src_handle;
universe@551 226 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 227
universe@551 228 // remove current element, if asked
universe@551 229 if (iter->remove) {
universe@551 230 // clear the flag
universe@551 231 iter->remove = false;
universe@551 232
universe@551 233 // determine the next element
universe@551 234 struct cx_hash_map_element_s *next = elm->next;
universe@551 235
universe@551 236 // search the previous element
universe@551 237 struct cx_hash_map_element_s *prev = NULL;
universe@551 238 if (map->buckets[iter->slot] != elm) {
universe@551 239 prev = map->buckets[iter->slot];
universe@551 240 while (prev->next != elm) {
universe@551 241 prev = prev->next;
universe@551 242 }
universe@551 243 }
universe@551 244
universe@551 245 // unlink
universe@551 246 cx_hash_map_unlink(map, iter->slot, prev, elm);
universe@551 247
universe@551 248 // advance
universe@551 249 elm = next;
universe@551 250 } else {
universe@551 251 // just advance
universe@551 252 elm = elm->next;
universe@560 253 iter->index++;
universe@551 254 }
universe@551 255
universe@560 256 // search the next bucket, if required
universe@560 257 while (elm == NULL && ++iter->slot < map->bucket_count) {
universe@560 258 elm = map->buckets[iter->slot];
universe@551 259 }
universe@551 260
universe@560 261 // fill the struct with the next element
universe@551 262 iter->elem_handle = elm;
universe@551 263 if (elm == NULL) {
universe@551 264 iter->kv_data.key = NULL;
universe@551 265 iter->kv_data.value = NULL;
universe@551 266 } else {
universe@551 267 iter->kv_data.key = &elm->key;
universe@551 268 // TODO: pointer to data if this map is storing copies
universe@551 269 iter->kv_data.value = elm->data;
universe@551 270 }
universe@551 271 }
universe@551 272
universe@551 273 static CxIterator cx_hash_map_iterator(CxMap *map) {
universe@550 274 CxIterator iter;
universe@551 275
universe@551 276 iter.src_handle = map;
universe@551 277 iter.valid = cx_hash_map_iter_valid;
universe@551 278 iter.next = cx_hash_map_iter_next;
universe@551 279 iter.current = cx_hash_map_iter_current_entry;
universe@551 280
universe@551 281 iter.slot = 0;
universe@551 282 iter.index = 0;
universe@551 283 iter.remove = false;
universe@551 284
universe@551 285 if (map->size > 0) {
universe@551 286 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@560 287 struct cx_hash_map_element_s *elm = hash_map->buckets[0];
universe@554 288 for (; elm == NULL; iter.slot++) {
universe@554 289 elm = hash_map->buckets[iter.slot];
universe@551 290 }
universe@554 291 iter.elem_handle = elm;
universe@554 292 iter.kv_data.key = &elm->key;
universe@554 293 // TODO: pointer to data if this map is storing copies
universe@554 294 iter.kv_data.value = elm->data;
universe@551 295 } else {
universe@551 296 iter.elem_handle = NULL;
universe@551 297 iter.kv_data.key = NULL;
universe@551 298 iter.kv_data.value = NULL;
universe@551 299 }
universe@551 300
universe@550 301 return iter;
universe@550 302 }
universe@550 303
universe@551 304 static CxIterator cx_hash_map_iterator_keys(CxMap *map) {
universe@551 305 CxIterator iter = cx_hash_map_iterator(map);
universe@551 306 iter.current = cx_hash_map_iter_current_key;
universe@550 307 return iter;
universe@550 308 }
universe@550 309
universe@551 310 static CxIterator cx_hash_map_iterator_values(CxMap *map) {
universe@551 311 CxIterator iter = cx_hash_map_iterator(map);
universe@551 312 iter.current = cx_hash_map_iter_current_value;
universe@550 313 return iter;
universe@550 314 }
universe@550 315
universe@550 316 static cx_map_class cx_hash_map_class = {
universe@550 317 cx_hash_map_destructor,
universe@550 318 cx_hash_map_clear,
universe@550 319 cx_hash_map_put,
universe@550 320 cx_hash_map_get,
universe@550 321 cx_hash_map_remove,
universe@550 322 cx_hash_map_iterator,
universe@550 323 cx_hash_map_iterator_keys,
universe@550 324 cx_hash_map_iterator_values,
universe@550 325 };
universe@550 326
universe@550 327 CxMap *cxHashMapCreate(
universe@550 328 CxAllocator *allocator,
universe@550 329 size_t buckets
universe@550 330 ) {
universe@550 331 if (buckets == 0) {
universe@550 332 // implementation defined default
universe@550 333 buckets = 16;
universe@550 334 }
universe@550 335
universe@550 336 struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s));
universe@550 337 if (map == NULL) return NULL;
universe@550 338
universe@550 339 // initialize hash map members
universe@550 340 map->bucket_count = buckets;
universe@550 341 map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *));
universe@550 342 if (map->buckets == NULL) {
universe@550 343 cxFree(allocator, map);
universe@550 344 return NULL;
universe@550 345 }
universe@550 346
universe@550 347 // initialize base members
universe@550 348 map->base.cl = &cx_hash_map_class;
universe@550 349 map->base.allocator = allocator;
universe@550 350 map->base.size = 0;
universe@550 351
universe@550 352 return (CxMap *) map;
universe@550 353 }
universe@562 354
universe@562 355 int cxMapRehash(CxMap *map) {
universe@562 356 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@562 357 if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
universe@562 358
universe@562 359 size_t new_bucket_count = (map->size * 5) >> 1;
universe@562 360 struct cx_hash_map_element_s **new_buckets = cxCalloc(map->allocator,
universe@562 361 new_bucket_count, sizeof(struct cx_hash_map_element_s *));
universe@562 362
universe@562 363 if (new_buckets == NULL) {
universe@562 364 return 1;
universe@562 365 }
universe@562 366
universe@562 367 // iterate through the elements and assign them to their new slots
universe@562 368 cx_for_n(slot, hash_map->bucket_count) {
universe@562 369 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@562 370 while (elm != NULL) {
universe@562 371 struct cx_hash_map_element_s *next = elm->next;
universe@562 372 size_t new_slot = elm->key.hash % new_bucket_count;
universe@562 373
universe@562 374 // find position where to insert
universe@562 375 struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
universe@562 376 struct cx_hash_map_element_s *bucket_prev = NULL;
universe@562 377 while (bucket_next != NULL && bucket_next->key.hash < elm->key.hash) {
universe@562 378 bucket_prev = bucket_next;
universe@562 379 bucket_next = bucket_next->next;
universe@562 380 }
universe@562 381
universe@562 382 // insert
universe@562 383 if (bucket_prev == NULL) {
universe@562 384 elm->next = new_buckets[new_slot];
universe@562 385 new_buckets[new_slot] = elm;
universe@562 386 } else {
universe@562 387 bucket_prev->next = elm;
universe@562 388 elm->next = bucket_next;
universe@562 389 }
universe@562 390
universe@562 391 // advance
universe@562 392 elm = next;
universe@562 393 }
universe@562 394 }
universe@562 395
universe@562 396 // assign result to the map
universe@562 397 hash_map->bucket_count = new_bucket_count;
universe@562 398 cxFree(map->allocator, hash_map->buckets);
universe@562 399 hash_map->buckets = new_buckets;
universe@562 400 }
universe@562 401 return 0;
universe@562 402 }

mercurial