src/hash_map.c

Sun, 21 May 2023 14:37:56 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 21 May 2023 14:37:56 +0200
changeset 706
8c6edaccaef1
parent 691
65baf7f45ac8
child 709
1e8ba59e7911
permissions
-rw-r--r--

add empty map implementation - fixes #259

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

mercurial