src/hash_map.c

Thu, 23 May 2024 19:29:14 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 23 May 2024 19:29:14 +0200
changeset 853
d4baf4dd55c3
parent 850
b2bc48c2b251
child 854
fe0d69d72bcd
permissions
-rw-r--r--

simplify iterator structures

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

mercurial