src/hash_map.c

Fri, 12 Apr 2024 21:48:12 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 12 Apr 2024 21:48:12 +0200
changeset 849
edb9f875b7f9
parent 829
7d4e31d295af
permissions
-rw-r--r--

improves interface of cx_sprintf() variants

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@658 255 struct cx_hash_map_s const *map = iter->src_handle;
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@551 272
universe@551 273 // remove current element, if asked
universe@630 274 if (iter->base.remove) {
universe@630 275 // obtain mutable pointer to the map
universe@630 276 struct cx_mut_iterator_s *miter = it;
universe@630 277 struct cx_hash_map_s *map = miter->src_handle;
universe@630 278
universe@551 279 // clear the flag
universe@630 280 iter->base.remove = false;
universe@551 281
universe@551 282 // determine the next element
universe@551 283 struct cx_hash_map_element_s *next = elm->next;
universe@551 284
universe@551 285 // search the previous element
universe@551 286 struct cx_hash_map_element_s *prev = NULL;
universe@551 287 if (map->buckets[iter->slot] != elm) {
universe@551 288 prev = map->buckets[iter->slot];
universe@551 289 while (prev->next != elm) {
universe@551 290 prev = prev->next;
universe@551 291 }
universe@551 292 }
universe@551 293
universe@686 294 // destroy
universe@686 295 cx_invoke_destructor((struct cx_map_s *) map, elm->data);
universe@686 296
universe@551 297 // unlink
universe@551 298 cx_hash_map_unlink(map, iter->slot, prev, elm);
universe@551 299
universe@551 300 // advance
universe@551 301 elm = next;
universe@551 302 } else {
universe@551 303 // just advance
universe@551 304 elm = elm->next;
universe@560 305 iter->index++;
universe@551 306 }
universe@551 307
universe@560 308 // search the next bucket, if required
universe@630 309 struct cx_hash_map_s const *map = iter->src_handle;
universe@560 310 while (elm == NULL && ++iter->slot < map->bucket_count) {
universe@560 311 elm = map->buckets[iter->slot];
universe@551 312 }
universe@551 313
universe@560 314 // fill the struct with the next element
universe@551 315 iter->elem_handle = elm;
universe@551 316 if (elm == NULL) {
universe@551 317 iter->kv_data.key = NULL;
universe@551 318 iter->kv_data.value = NULL;
universe@551 319 } else {
universe@551 320 iter->kv_data.key = &elm->key;
universe@685 321 if (map->base.store_pointer) {
universe@658 322 iter->kv_data.value = *(void **) elm->data;
universe@658 323 } else {
universe@658 324 iter->kv_data.value = elm->data;
universe@658 325 }
universe@551 326 }
universe@551 327 }
universe@551 328
universe@709 329 static CxIterator cx_hash_map_iterator(
universe@709 330 CxMap const *map,
universe@709 331 enum cx_map_iterator_type type
universe@709 332 ) {
universe@550 333 CxIterator iter;
universe@551 334
universe@551 335 iter.src_handle = map;
universe@630 336 iter.base.valid = cx_hash_map_iter_valid;
universe@630 337 iter.base.next = cx_hash_map_iter_next;
universe@709 338
universe@709 339 switch (type) {
universe@709 340 case CX_MAP_ITERATOR_PAIRS:
universe@709 341 iter.base.current = cx_hash_map_iter_current_entry;
universe@709 342 break;
universe@709 343 case CX_MAP_ITERATOR_KEYS:
universe@709 344 iter.base.current = cx_hash_map_iter_current_key;
universe@709 345 break;
universe@709 346 case CX_MAP_ITERATOR_VALUES:
universe@709 347 iter.base.current = cx_hash_map_iter_current_value;
universe@709 348 break;
universe@709 349 default:
universe@709 350 assert(false);
universe@709 351 }
universe@709 352
universe@630 353 iter.base.remove = false;
universe@630 354 iter.base.mutating = false;
universe@551 355
universe@551 356 iter.slot = 0;
universe@551 357 iter.index = 0;
universe@551 358
universe@551 359 if (map->size > 0) {
universe@551 360 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@560 361 struct cx_hash_map_element_s *elm = hash_map->buckets[0];
olaf@665 362 while (elm == NULL) {
olaf@665 363 elm = hash_map->buckets[++iter.slot];
universe@551 364 }
universe@554 365 iter.elem_handle = elm;
universe@554 366 iter.kv_data.key = &elm->key;
universe@685 367 if (map->store_pointer) {
universe@658 368 iter.kv_data.value = *(void **) elm->data;
universe@658 369 } else {
universe@658 370 iter.kv_data.value = elm->data;
universe@658 371 }
universe@551 372 } else {
universe@551 373 iter.elem_handle = NULL;
universe@551 374 iter.kv_data.key = NULL;
universe@551 375 iter.kv_data.value = NULL;
universe@551 376 }
universe@551 377
universe@550 378 return iter;
universe@550 379 }
universe@550 380
universe@550 381 static cx_map_class cx_hash_map_class = {
universe@550 382 cx_hash_map_destructor,
universe@550 383 cx_hash_map_clear,
universe@550 384 cx_hash_map_put,
universe@550 385 cx_hash_map_get,
universe@550 386 cx_hash_map_remove,
universe@550 387 cx_hash_map_iterator,
universe@550 388 };
universe@550 389
universe@550 390 CxMap *cxHashMapCreate(
universe@691 391 CxAllocator const *allocator,
universe@658 392 size_t itemsize,
universe@550 393 size_t buckets
universe@550 394 ) {
universe@550 395 if (buckets == 0) {
universe@550 396 // implementation defined default
universe@550 397 buckets = 16;
universe@550 398 }
universe@550 399
universe@688 400 struct cx_hash_map_s *map = cxCalloc(allocator, 1,
universe@688 401 sizeof(struct cx_hash_map_s));
universe@550 402 if (map == NULL) return NULL;
universe@550 403
universe@550 404 // initialize hash map members
universe@550 405 map->bucket_count = buckets;
universe@688 406 map->buckets = cxCalloc(allocator, buckets,
universe@688 407 sizeof(struct cx_hash_map_element_s *));
universe@550 408 if (map->buckets == NULL) {
universe@550 409 cxFree(allocator, map);
universe@550 410 return NULL;
universe@550 411 }
universe@550 412
universe@550 413 // initialize base members
universe@550 414 map->base.cl = &cx_hash_map_class;
universe@550 415 map->base.allocator = allocator;
universe@668 416
universe@668 417 if (itemsize > 0) {
universe@685 418 map->base.store_pointer = false;
universe@677 419 map->base.item_size = itemsize;
universe@668 420 } else {
universe@685 421 map->base.store_pointer = true;
universe@677 422 map->base.item_size = sizeof(void *);
universe@668 423 }
universe@550 424
universe@550 425 return (CxMap *) map;
universe@550 426 }
universe@562 427
universe@562 428 int cxMapRehash(CxMap *map) {
universe@562 429 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@562 430 if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
universe@562 431
universe@562 432 size_t new_bucket_count = (map->size * 5) >> 1;
universe@688 433 struct cx_hash_map_element_s **new_buckets = cxCalloc(
universe@688 434 map->allocator,
universe@688 435 new_bucket_count, sizeof(struct cx_hash_map_element_s *)
universe@688 436 );
universe@562 437
universe@562 438 if (new_buckets == NULL) {
universe@562 439 return 1;
universe@562 440 }
universe@562 441
universe@562 442 // iterate through the elements and assign them to their new slots
universe@562 443 cx_for_n(slot, hash_map->bucket_count) {
universe@562 444 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@562 445 while (elm != NULL) {
universe@562 446 struct cx_hash_map_element_s *next = elm->next;
universe@562 447 size_t new_slot = elm->key.hash % new_bucket_count;
universe@562 448
universe@562 449 // find position where to insert
universe@562 450 struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
universe@562 451 struct cx_hash_map_element_s *bucket_prev = NULL;
universe@688 452 while (bucket_next != NULL &&
universe@688 453 bucket_next->key.hash < elm->key.hash) {
universe@562 454 bucket_prev = bucket_next;
universe@562 455 bucket_next = bucket_next->next;
universe@562 456 }
universe@562 457
universe@562 458 // insert
universe@562 459 if (bucket_prev == NULL) {
universe@562 460 elm->next = new_buckets[new_slot];
universe@562 461 new_buckets[new_slot] = elm;
universe@562 462 } else {
universe@562 463 bucket_prev->next = elm;
universe@562 464 elm->next = bucket_next;
universe@562 465 }
universe@562 466
universe@562 467 // advance
universe@562 468 elm = next;
universe@562 469 }
universe@562 470 }
universe@562 471
universe@562 472 // assign result to the map
universe@562 473 hash_map->bucket_count = new_bucket_count;
universe@562 474 cxFree(map->allocator, hash_map->buckets);
universe@562 475 hash_map->buckets = new_buckets;
universe@562 476 }
universe@562 477 return 0;
universe@562 478 }

mercurial