src/hash_map.c

Mon, 03 Apr 2023 19:09:31 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 03 Apr 2023 19:09:31 +0200
changeset 673
60fb6aec157d
parent 669
dce9b8450656
child 677
b09aae58bba4
permissions
-rw-r--r--

make allocator in cxBufferInit optional

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@658 33 struct cx_hash_map_element_s {
universe@658 34 /** A pointer to the next element in the current bucket. */
universe@658 35 struct cx_hash_map_element_s *next;
universe@658 36
universe@658 37 /** The corresponding key. */
universe@658 38 CxHashKey key;
universe@658 39
universe@658 40 /** The value data. */
universe@658 41 char data[];
universe@658 42 };
universe@658 43
universe@550 44 static void cx_hash_map_clear(struct cx_map_s *map) {
universe@550 45 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 46 cx_for_n(i, hash_map->bucket_count) {
universe@550 47 struct cx_hash_map_element_s *elem = hash_map->buckets[i];
universe@550 48 if (elem != NULL) {
universe@550 49 do {
universe@550 50 struct cx_hash_map_element_s *next = elem->next;
universe@550 51 // free the key data
universe@563 52 cxFree(map->allocator, elem->key.data.obj);
universe@550 53 // free the node
universe@550 54 cxFree(map->allocator, elem);
universe@550 55 // proceed
universe@550 56 elem = next;
universe@550 57 } while (elem != NULL);
universe@550 58
universe@550 59 // do not leave a dangling pointer
universe@550 60 hash_map->buckets[i] = NULL;
universe@550 61 }
universe@550 62 }
universe@550 63 map->size = 0;
universe@550 64 }
universe@550 65
universe@550 66 static void cx_hash_map_destructor(struct cx_map_s *map) {
universe@550 67 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 68
universe@550 69 // free the buckets
universe@550 70 cx_hash_map_clear(map);
universe@550 71 cxFree(map->allocator, hash_map->buckets);
universe@550 72
universe@550 73 // free the map structure
universe@550 74 cxFree(map->allocator, map);
universe@550 75 }
universe@550 76
universe@550 77 static int cx_hash_map_put(
universe@550 78 CxMap *map,
universe@563 79 CxHashKey key,
universe@550 80 void *value
universe@550 81 ) {
universe@550 82 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 83 CxAllocator *allocator = map->allocator;
universe@550 84
universe@563 85 unsigned hash = key.hash;
universe@563 86 if (hash == 0) {
universe@563 87 cx_hash_murmur(&key);
universe@563 88 hash = key.hash;
universe@563 89 }
universe@550 90
universe@550 91 size_t slot = hash % hash_map->bucket_count;
universe@550 92 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 93 struct cx_hash_map_element_s *prev = NULL;
universe@550 94
universe@550 95 while (elm != NULL && elm->key.hash < hash) {
universe@550 96 prev = elm;
universe@550 97 elm = elm->next;
universe@550 98 }
universe@550 99
universe@575 100 if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
universe@575 101 memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) {
universe@574 102 // overwrite existing element
universe@658 103 if (map->store_pointers) {
universe@658 104 memcpy(elm->data, &value, sizeof(void *));
universe@658 105 } else {
universe@658 106 memcpy(elm->data, value, map->itemsize);
universe@658 107 }
universe@574 108 } else {
universe@575 109 // allocate new element
universe@658 110 struct cx_hash_map_element_s *e = cxMalloc(
universe@658 111 allocator,
universe@658 112 sizeof(struct cx_hash_map_element_s) + map->itemsize
universe@658 113 );
universe@550 114 if (e == NULL) {
universe@550 115 return -1;
universe@550 116 }
universe@550 117
universe@550 118 // write the value
universe@658 119 if (map->store_pointers) {
universe@658 120 memcpy(e->data, &value, sizeof(void *));
universe@658 121 } else {
universe@658 122 memcpy(e->data, value, map->itemsize);
universe@658 123 }
universe@550 124
universe@550 125 // copy the key
universe@550 126 void *kd = cxMalloc(allocator, key.len);
universe@550 127 if (kd == NULL) {
universe@550 128 return -1;
universe@550 129 }
universe@563 130 memcpy(kd, key.data.obj, key.len);
universe@563 131 e->key.data.obj = kd;
universe@550 132 e->key.len = key.len;
universe@550 133 e->key.hash = hash;
universe@550 134
universe@550 135 // insert the element into the linked list
universe@550 136 if (prev == NULL) {
universe@550 137 hash_map->buckets[slot] = e;
universe@550 138 } else {
universe@550 139 prev->next = e;
universe@550 140 }
universe@550 141 e->next = elm;
universe@550 142
universe@550 143 // increase the size
universe@550 144 map->size++;
universe@550 145 }
universe@550 146
universe@550 147 return 0;
universe@550 148 }
universe@549 149
universe@551 150 static void cx_hash_map_unlink(
universe@551 151 struct cx_hash_map_s *hash_map,
universe@551 152 size_t slot,
universe@551 153 struct cx_hash_map_element_s *prev,
universe@551 154 struct cx_hash_map_element_s *elm
universe@551 155 ) {
universe@551 156 // unlink
universe@551 157 if (prev == NULL) {
universe@551 158 hash_map->buckets[slot] = elm->next;
universe@551 159 } else {
universe@551 160 prev->next = elm->next;
universe@551 161 }
universe@551 162 // free element
universe@563 163 cxFree(hash_map->base.allocator, elm->key.data.obj);
universe@551 164 cxFree(hash_map->base.allocator, elm);
universe@551 165 // decrease size
universe@551 166 hash_map->base.size--;
universe@551 167 }
universe@551 168
universe@549 169 /**
universe@550 170 * Helper function to avoid code duplication.
universe@549 171 *
universe@550 172 * @param map the map
universe@550 173 * @param key the key to look up
universe@550 174 * @param remove flag indicating whether the looked up entry shall be removed
universe@658 175 * @return a pointer to the value corresponding to the key or \c NULL
universe@549 176 */
universe@550 177 static void *cx_hash_map_get_remove(
universe@550 178 CxMap *map,
universe@563 179 CxHashKey key,
universe@550 180 bool remove
universe@550 181 ) {
universe@550 182 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 183
universe@563 184 unsigned hash = key.hash;
universe@563 185 if (hash == 0) {
universe@563 186 cx_hash_murmur(&key);
universe@563 187 hash = key.hash;
universe@563 188 }
universe@550 189
universe@550 190 size_t slot = hash % hash_map->bucket_count;
universe@550 191 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 192 struct cx_hash_map_element_s *prev = NULL;
universe@550 193 while (elm && elm->key.hash <= hash) {
universe@550 194 if (elm->key.hash == hash && elm->key.len == key.len) {
universe@563 195 if (memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) {
universe@658 196 void *data = NULL;
universe@658 197 if (map->store_pointers) {
universe@658 198 data = *(void **) elm->data;
universe@658 199 } else if (!remove) {
universe@658 200 data = elm->data;
universe@658 201 }
universe@550 202 if (remove) {
universe@551 203 cx_hash_map_unlink(hash_map, slot, prev, elm);
universe@550 204 }
universe@550 205 return data;
universe@550 206 }
universe@550 207 }
universe@550 208 prev = elm;
universe@550 209 elm = prev->next;
universe@550 210 }
universe@550 211
universe@550 212 return NULL;
universe@549 213 }
universe@550 214
universe@550 215 static void *cx_hash_map_get(
universe@550 216 CxMap const *map,
universe@563 217 CxHashKey key
universe@550 218 ) {
universe@550 219 // we can safely cast, because we know when remove=false, the map stays untouched
universe@550 220 return cx_hash_map_get_remove((CxMap *) map, key, false);
universe@550 221 }
universe@550 222
universe@550 223 static void *cx_hash_map_remove(
universe@550 224 CxMap *map,
universe@563 225 CxHashKey key
universe@550 226 ) {
universe@550 227 return cx_hash_map_get_remove(map, key, true);
universe@550 228 }
universe@550 229
universe@630 230 static void *cx_hash_map_iter_current_entry(void const *it) {
universe@630 231 struct cx_iterator_s const *iter = it;
universe@551 232 // struct has to have a compatible signature
universe@573 233 return (struct cx_map_entry_s *) &(iter->kv_data);
universe@551 234 }
universe@551 235
universe@630 236 static void *cx_hash_map_iter_current_key(void const *it) {
universe@630 237 struct cx_iterator_s const *iter = it;
universe@551 238 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 239 return &elm->key;
universe@551 240 }
universe@551 241
universe@630 242 static void *cx_hash_map_iter_current_value(void const *it) {
universe@630 243 struct cx_iterator_s const *iter = it;
universe@658 244 struct cx_hash_map_s const *map = iter->src_handle;
universe@551 245 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@658 246 if (map->base.store_pointers) {
universe@658 247 return *(void **) elm->data;
universe@658 248 } else {
universe@658 249 return elm->data;
universe@658 250 }
universe@551 251 }
universe@551 252
universe@630 253 static bool cx_hash_map_iter_valid(void const *it) {
universe@630 254 struct cx_iterator_s const *iter = it;
universe@551 255 return iter->elem_handle != NULL;
universe@551 256 }
universe@551 257
universe@630 258 static void cx_hash_map_iter_next(void *it) {
universe@630 259 struct cx_iterator_s *iter = it;
universe@551 260 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 261
universe@551 262 // remove current element, if asked
universe@630 263 if (iter->base.remove) {
universe@630 264 // obtain mutable pointer to the map
universe@630 265 struct cx_mut_iterator_s *miter = it;
universe@630 266 struct cx_hash_map_s *map = miter->src_handle;
universe@630 267
universe@551 268 // clear the flag
universe@630 269 iter->base.remove = false;
universe@551 270
universe@551 271 // determine the next element
universe@551 272 struct cx_hash_map_element_s *next = elm->next;
universe@551 273
universe@551 274 // search the previous element
universe@551 275 struct cx_hash_map_element_s *prev = NULL;
universe@551 276 if (map->buckets[iter->slot] != elm) {
universe@551 277 prev = map->buckets[iter->slot];
universe@551 278 while (prev->next != elm) {
universe@551 279 prev = prev->next;
universe@551 280 }
universe@551 281 }
universe@551 282
universe@551 283 // unlink
universe@551 284 cx_hash_map_unlink(map, iter->slot, prev, elm);
universe@551 285
universe@551 286 // advance
universe@551 287 elm = next;
universe@551 288 } else {
universe@551 289 // just advance
universe@551 290 elm = elm->next;
universe@560 291 iter->index++;
universe@551 292 }
universe@551 293
universe@560 294 // search the next bucket, if required
universe@630 295 struct cx_hash_map_s const *map = iter->src_handle;
universe@560 296 while (elm == NULL && ++iter->slot < map->bucket_count) {
universe@560 297 elm = map->buckets[iter->slot];
universe@551 298 }
universe@551 299
universe@560 300 // fill the struct with the next element
universe@551 301 iter->elem_handle = elm;
universe@551 302 if (elm == NULL) {
universe@551 303 iter->kv_data.key = NULL;
universe@551 304 iter->kv_data.value = NULL;
universe@551 305 } else {
universe@551 306 iter->kv_data.key = &elm->key;
universe@658 307 if (map->base.store_pointers) {
universe@658 308 iter->kv_data.value = *(void **) elm->data;
universe@658 309 } else {
universe@658 310 iter->kv_data.value = elm->data;
universe@658 311 }
universe@551 312 }
universe@551 313 }
universe@551 314
universe@630 315 static bool cx_hash_map_iter_flag_rm(void *it) {
universe@630 316 struct cx_iterator_base_s *iter = it;
universe@630 317 if (iter->mutating) {
universe@630 318 iter->remove = true;
universe@630 319 return true;
universe@630 320 } else {
universe@630 321 return false;
universe@630 322 }
universe@630 323 }
universe@630 324
universe@630 325 static CxIterator cx_hash_map_iterator(CxMap const *map) {
universe@550 326 CxIterator iter;
universe@551 327
universe@551 328 iter.src_handle = map;
universe@630 329 iter.base.valid = cx_hash_map_iter_valid;
universe@630 330 iter.base.next = cx_hash_map_iter_next;
universe@630 331 iter.base.current = cx_hash_map_iter_current_entry;
universe@630 332 iter.base.flag_removal = cx_hash_map_iter_flag_rm;
universe@630 333 iter.base.remove = false;
universe@630 334 iter.base.mutating = false;
universe@551 335
universe@551 336 iter.slot = 0;
universe@551 337 iter.index = 0;
universe@551 338
universe@551 339 if (map->size > 0) {
universe@551 340 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@560 341 struct cx_hash_map_element_s *elm = hash_map->buckets[0];
olaf@665 342 while (elm == NULL) {
olaf@665 343 elm = hash_map->buckets[++iter.slot];
universe@551 344 }
universe@554 345 iter.elem_handle = elm;
universe@554 346 iter.kv_data.key = &elm->key;
universe@658 347 if (map->store_pointers) {
universe@658 348 iter.kv_data.value = *(void **) elm->data;
universe@658 349 } else {
universe@658 350 iter.kv_data.value = elm->data;
universe@658 351 }
universe@551 352 } else {
universe@551 353 iter.elem_handle = NULL;
universe@551 354 iter.kv_data.key = NULL;
universe@551 355 iter.kv_data.value = NULL;
universe@551 356 }
universe@551 357
universe@550 358 return iter;
universe@550 359 }
universe@550 360
universe@630 361 static CxIterator cx_hash_map_iterator_keys(CxMap const *map) {
universe@551 362 CxIterator iter = cx_hash_map_iterator(map);
universe@630 363 iter.base.current = cx_hash_map_iter_current_key;
universe@550 364 return iter;
universe@550 365 }
universe@550 366
universe@630 367 static CxIterator cx_hash_map_iterator_values(CxMap const *map) {
universe@551 368 CxIterator iter = cx_hash_map_iterator(map);
universe@630 369 iter.base.current = cx_hash_map_iter_current_value;
universe@630 370 return iter;
universe@630 371 }
universe@630 372
universe@630 373 static CxMutIterator cx_hash_map_mut_iterator(CxMap *map) {
universe@630 374 CxIterator it = cx_hash_map_iterator(map);
universe@630 375 it.base.mutating = true;
universe@630 376
universe@630 377 // we know the iterators share the same memory layout
universe@630 378 CxMutIterator iter;
universe@630 379 memcpy(&iter, &it, sizeof(CxMutIterator));
universe@630 380 return iter;
universe@630 381 }
universe@630 382
universe@630 383 static CxMutIterator cx_hash_map_mut_iterator_keys(CxMap *map) {
universe@630 384 CxMutIterator iter = cx_hash_map_mut_iterator(map);
universe@630 385 iter.base.current = cx_hash_map_iter_current_key;
universe@630 386 return iter;
universe@630 387 }
universe@630 388
universe@630 389 static CxMutIterator cx_hash_map_mut_iterator_values(CxMap *map) {
universe@630 390 CxMutIterator iter = cx_hash_map_mut_iterator(map);
universe@630 391 iter.base.current = cx_hash_map_iter_current_value;
universe@550 392 return iter;
universe@550 393 }
universe@550 394
universe@550 395 static cx_map_class cx_hash_map_class = {
universe@550 396 cx_hash_map_destructor,
universe@550 397 cx_hash_map_clear,
universe@550 398 cx_hash_map_put,
universe@550 399 cx_hash_map_get,
universe@550 400 cx_hash_map_remove,
universe@550 401 cx_hash_map_iterator,
universe@550 402 cx_hash_map_iterator_keys,
universe@550 403 cx_hash_map_iterator_values,
universe@630 404 cx_hash_map_mut_iterator,
universe@630 405 cx_hash_map_mut_iterator_keys,
universe@630 406 cx_hash_map_mut_iterator_values,
universe@550 407 };
universe@550 408
universe@550 409 CxMap *cxHashMapCreate(
universe@550 410 CxAllocator *allocator,
universe@658 411 size_t itemsize,
universe@550 412 size_t buckets
universe@550 413 ) {
universe@550 414 if (buckets == 0) {
universe@550 415 // implementation defined default
universe@550 416 buckets = 16;
universe@550 417 }
universe@550 418
universe@550 419 struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s));
universe@550 420 if (map == NULL) return NULL;
universe@550 421
universe@550 422 // initialize hash map members
universe@550 423 map->bucket_count = buckets;
universe@550 424 map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *));
universe@550 425 if (map->buckets == NULL) {
universe@550 426 cxFree(allocator, map);
universe@550 427 return NULL;
universe@550 428 }
universe@550 429
universe@550 430 // initialize base members
universe@550 431 map->base.cl = &cx_hash_map_class;
universe@550 432 map->base.allocator = allocator;
universe@550 433 map->base.size = 0;
universe@668 434
universe@668 435 if (itemsize > 0) {
universe@669 436 map->base.store_pointers = false;
universe@668 437 map->base.itemsize = itemsize;
universe@668 438 } else {
universe@669 439 map->base.store_pointers = true;
universe@669 440 map->base.itemsize = sizeof(void *);
universe@668 441 }
universe@550 442
universe@550 443 return (CxMap *) map;
universe@550 444 }
universe@562 445
universe@562 446 int cxMapRehash(CxMap *map) {
universe@562 447 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@562 448 if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
universe@562 449
universe@562 450 size_t new_bucket_count = (map->size * 5) >> 1;
universe@562 451 struct cx_hash_map_element_s **new_buckets = cxCalloc(map->allocator,
universe@562 452 new_bucket_count, sizeof(struct cx_hash_map_element_s *));
universe@562 453
universe@562 454 if (new_buckets == NULL) {
universe@562 455 return 1;
universe@562 456 }
universe@562 457
universe@562 458 // iterate through the elements and assign them to their new slots
universe@562 459 cx_for_n(slot, hash_map->bucket_count) {
universe@562 460 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@562 461 while (elm != NULL) {
universe@562 462 struct cx_hash_map_element_s *next = elm->next;
universe@562 463 size_t new_slot = elm->key.hash % new_bucket_count;
universe@562 464
universe@562 465 // find position where to insert
universe@562 466 struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
universe@562 467 struct cx_hash_map_element_s *bucket_prev = NULL;
universe@562 468 while (bucket_next != NULL && bucket_next->key.hash < elm->key.hash) {
universe@562 469 bucket_prev = bucket_next;
universe@562 470 bucket_next = bucket_next->next;
universe@562 471 }
universe@562 472
universe@562 473 // insert
universe@562 474 if (bucket_prev == NULL) {
universe@562 475 elm->next = new_buckets[new_slot];
universe@562 476 new_buckets[new_slot] = elm;
universe@562 477 } else {
universe@562 478 bucket_prev->next = elm;
universe@562 479 elm->next = bucket_next;
universe@562 480 }
universe@562 481
universe@562 482 // advance
universe@562 483 elm = next;
universe@562 484 }
universe@562 485 }
universe@562 486
universe@562 487 // assign result to the map
universe@562 488 hash_map->bucket_count = new_bucket_count;
universe@562 489 cxFree(map->allocator, hash_map->buckets);
universe@562 490 hash_map->buckets = new_buckets;
universe@562 491 }
universe@562 492 return 0;
universe@562 493 }

mercurial