src/hash_map.c

Wed, 08 Jun 2022 21:33:31 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 08 Jun 2022 21:33:31 +0200
changeset 563
69a83fad8a35
parent 562
fd3368c20413
child 573
3f3a0d19db58
permissions
-rw-r--r--

improve hash key handling

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@550 89 if (elm == NULL || elm->key.hash != hash) {
universe@550 90 struct cx_hash_map_element_s *e = cxMalloc(allocator, sizeof(struct cx_hash_map_element_s));
universe@550 91 if (e == NULL) {
universe@550 92 return -1;
universe@550 93 }
universe@550 94
universe@550 95 // write the value
universe@550 96 // TODO: depending on future map features, we may want to copy here
universe@550 97 e->data = value;
universe@550 98
universe@550 99 // copy the key
universe@550 100 void *kd = cxMalloc(allocator, key.len);
universe@550 101 if (kd == NULL) {
universe@550 102 return -1;
universe@550 103 }
universe@563 104 memcpy(kd, key.data.obj, key.len);
universe@563 105 e->key.data.obj = kd;
universe@550 106 e->key.len = key.len;
universe@550 107 e->key.hash = hash;
universe@550 108
universe@550 109 // insert the element into the linked list
universe@550 110 if (prev == NULL) {
universe@550 111 hash_map->buckets[slot] = e;
universe@550 112 } else {
universe@550 113 prev->next = e;
universe@550 114 }
universe@550 115 e->next = elm;
universe@550 116
universe@550 117 // increase the size
universe@550 118 map->size++;
universe@550 119 } else {
universe@550 120 // (elem != NULL && elem->key.hash == hash) - overwrite value of existing element
universe@550 121 elm->data = value;
universe@550 122 }
universe@550 123
universe@550 124 return 0;
universe@550 125 }
universe@549 126
universe@551 127 static void cx_hash_map_unlink(
universe@551 128 struct cx_hash_map_s *hash_map,
universe@551 129 size_t slot,
universe@551 130 struct cx_hash_map_element_s *prev,
universe@551 131 struct cx_hash_map_element_s *elm
universe@551 132 ) {
universe@551 133 // unlink
universe@551 134 if (prev == NULL) {
universe@551 135 hash_map->buckets[slot] = elm->next;
universe@551 136 } else {
universe@551 137 prev->next = elm->next;
universe@551 138 }
universe@551 139 // free element
universe@563 140 cxFree(hash_map->base.allocator, elm->key.data.obj);
universe@551 141 cxFree(hash_map->base.allocator, elm);
universe@551 142 // decrease size
universe@551 143 hash_map->base.size--;
universe@551 144 }
universe@551 145
universe@549 146 /**
universe@550 147 * Helper function to avoid code duplication.
universe@549 148 *
universe@550 149 * @param map the map
universe@550 150 * @param key the key to look up
universe@550 151 * @param remove flag indicating whether the looked up entry shall be removed
universe@550 152 * @return the value corresponding to the key or \c NULL
universe@549 153 */
universe@550 154 static void *cx_hash_map_get_remove(
universe@550 155 CxMap *map,
universe@563 156 CxHashKey key,
universe@550 157 bool remove
universe@550 158 ) {
universe@550 159 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 160
universe@563 161 unsigned hash = key.hash;
universe@563 162 if (hash == 0) {
universe@563 163 cx_hash_murmur(&key);
universe@563 164 hash = key.hash;
universe@563 165 }
universe@550 166
universe@550 167 size_t slot = hash % hash_map->bucket_count;
universe@550 168 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 169 struct cx_hash_map_element_s *prev = NULL;
universe@550 170 while (elm && elm->key.hash <= hash) {
universe@550 171 if (elm->key.hash == hash && elm->key.len == key.len) {
universe@563 172 if (memcmp(elm->key.data.obj, key.data.obj, key.len) == 0) {
universe@550 173 void *data = elm->data;
universe@550 174 if (remove) {
universe@551 175 cx_hash_map_unlink(hash_map, slot, prev, elm);
universe@550 176 }
universe@550 177 return data;
universe@550 178 }
universe@550 179 }
universe@550 180 prev = elm;
universe@550 181 elm = prev->next;
universe@550 182 }
universe@550 183
universe@550 184 return NULL;
universe@549 185 }
universe@550 186
universe@550 187 static void *cx_hash_map_get(
universe@550 188 CxMap const *map,
universe@563 189 CxHashKey key
universe@550 190 ) {
universe@550 191 // we can safely cast, because we know when remove=false, the map stays untouched
universe@550 192 return cx_hash_map_get_remove((CxMap *) map, key, false);
universe@550 193 }
universe@550 194
universe@550 195 static void *cx_hash_map_remove(
universe@550 196 CxMap *map,
universe@563 197 CxHashKey key
universe@550 198 ) {
universe@550 199 return cx_hash_map_get_remove(map, key, true);
universe@550 200 }
universe@550 201
universe@551 202 static void *cx_hash_map_iter_current_entry(CxIterator const *iter) {
universe@551 203 // struct has to have a compatible signature
universe@551 204 struct cx_map_entry_s *entry = (struct cx_map_entry_s *) &(iter->kv_data);
universe@551 205 return entry;
universe@551 206 }
universe@551 207
universe@551 208 static void *cx_hash_map_iter_current_key(CxIterator const *iter) {
universe@551 209 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 210 return &elm->key;
universe@551 211 }
universe@551 212
universe@551 213 static void *cx_hash_map_iter_current_value(CxIterator const *iter) {
universe@551 214 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 215 // TODO: return a pointer to data if this map is storing copies
universe@551 216 return elm->data;
universe@551 217 }
universe@551 218
universe@551 219 static bool cx_hash_map_iter_valid(CxIterator const *iter) {
universe@551 220 return iter->elem_handle != NULL;
universe@551 221 }
universe@551 222
universe@551 223 static void cx_hash_map_iter_next(CxIterator *iter) {
universe@551 224 struct cx_hash_map_s *map = iter->src_handle;
universe@551 225 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 226
universe@551 227 // remove current element, if asked
universe@551 228 if (iter->remove) {
universe@551 229 // clear the flag
universe@551 230 iter->remove = false;
universe@551 231
universe@551 232 // determine the next element
universe@551 233 struct cx_hash_map_element_s *next = elm->next;
universe@551 234
universe@551 235 // search the previous element
universe@551 236 struct cx_hash_map_element_s *prev = NULL;
universe@551 237 if (map->buckets[iter->slot] != elm) {
universe@551 238 prev = map->buckets[iter->slot];
universe@551 239 while (prev->next != elm) {
universe@551 240 prev = prev->next;
universe@551 241 }
universe@551 242 }
universe@551 243
universe@551 244 // unlink
universe@551 245 cx_hash_map_unlink(map, iter->slot, prev, elm);
universe@551 246
universe@551 247 // advance
universe@551 248 elm = next;
universe@551 249 } else {
universe@551 250 // just advance
universe@551 251 elm = elm->next;
universe@560 252 iter->index++;
universe@551 253 }
universe@551 254
universe@560 255 // search the next bucket, if required
universe@560 256 while (elm == NULL && ++iter->slot < map->bucket_count) {
universe@560 257 elm = map->buckets[iter->slot];
universe@551 258 }
universe@551 259
universe@560 260 // fill the struct with the next element
universe@551 261 iter->elem_handle = elm;
universe@551 262 if (elm == NULL) {
universe@551 263 iter->kv_data.key = NULL;
universe@551 264 iter->kv_data.value = NULL;
universe@551 265 } else {
universe@551 266 iter->kv_data.key = &elm->key;
universe@551 267 // TODO: pointer to data if this map is storing copies
universe@551 268 iter->kv_data.value = elm->data;
universe@551 269 }
universe@551 270 }
universe@551 271
universe@551 272 static CxIterator cx_hash_map_iterator(CxMap *map) {
universe@550 273 CxIterator iter;
universe@551 274
universe@551 275 iter.src_handle = map;
universe@551 276 iter.valid = cx_hash_map_iter_valid;
universe@551 277 iter.next = cx_hash_map_iter_next;
universe@551 278 iter.current = cx_hash_map_iter_current_entry;
universe@551 279
universe@551 280 iter.slot = 0;
universe@551 281 iter.index = 0;
universe@551 282 iter.remove = false;
universe@551 283
universe@551 284 if (map->size > 0) {
universe@551 285 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@560 286 struct cx_hash_map_element_s *elm = hash_map->buckets[0];
universe@554 287 for (; elm == NULL; iter.slot++) {
universe@554 288 elm = hash_map->buckets[iter.slot];
universe@551 289 }
universe@554 290 iter.elem_handle = elm;
universe@554 291 iter.kv_data.key = &elm->key;
universe@554 292 // TODO: pointer to data if this map is storing copies
universe@554 293 iter.kv_data.value = elm->data;
universe@551 294 } else {
universe@551 295 iter.elem_handle = NULL;
universe@551 296 iter.kv_data.key = NULL;
universe@551 297 iter.kv_data.value = NULL;
universe@551 298 }
universe@551 299
universe@550 300 return iter;
universe@550 301 }
universe@550 302
universe@551 303 static CxIterator cx_hash_map_iterator_keys(CxMap *map) {
universe@551 304 CxIterator iter = cx_hash_map_iterator(map);
universe@551 305 iter.current = cx_hash_map_iter_current_key;
universe@550 306 return iter;
universe@550 307 }
universe@550 308
universe@551 309 static CxIterator cx_hash_map_iterator_values(CxMap *map) {
universe@551 310 CxIterator iter = cx_hash_map_iterator(map);
universe@551 311 iter.current = cx_hash_map_iter_current_value;
universe@550 312 return iter;
universe@550 313 }
universe@550 314
universe@550 315 static cx_map_class cx_hash_map_class = {
universe@550 316 cx_hash_map_destructor,
universe@550 317 cx_hash_map_clear,
universe@550 318 cx_hash_map_put,
universe@550 319 cx_hash_map_get,
universe@550 320 cx_hash_map_remove,
universe@550 321 cx_hash_map_iterator,
universe@550 322 cx_hash_map_iterator_keys,
universe@550 323 cx_hash_map_iterator_values,
universe@550 324 };
universe@550 325
universe@550 326 CxMap *cxHashMapCreate(
universe@550 327 CxAllocator *allocator,
universe@550 328 size_t buckets
universe@550 329 ) {
universe@550 330 if (buckets == 0) {
universe@550 331 // implementation defined default
universe@550 332 buckets = 16;
universe@550 333 }
universe@550 334
universe@550 335 struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s));
universe@550 336 if (map == NULL) return NULL;
universe@550 337
universe@550 338 // initialize hash map members
universe@550 339 map->bucket_count = buckets;
universe@550 340 map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *));
universe@550 341 if (map->buckets == NULL) {
universe@550 342 cxFree(allocator, map);
universe@550 343 return NULL;
universe@550 344 }
universe@550 345
universe@550 346 // initialize base members
universe@550 347 map->base.cl = &cx_hash_map_class;
universe@550 348 map->base.allocator = allocator;
universe@550 349 map->base.size = 0;
universe@550 350
universe@550 351 return (CxMap *) map;
universe@550 352 }
universe@562 353
universe@562 354 int cxMapRehash(CxMap *map) {
universe@562 355 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@562 356 if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
universe@562 357
universe@562 358 size_t new_bucket_count = (map->size * 5) >> 1;
universe@562 359 struct cx_hash_map_element_s **new_buckets = cxCalloc(map->allocator,
universe@562 360 new_bucket_count, sizeof(struct cx_hash_map_element_s *));
universe@562 361
universe@562 362 if (new_buckets == NULL) {
universe@562 363 return 1;
universe@562 364 }
universe@562 365
universe@562 366 // iterate through the elements and assign them to their new slots
universe@562 367 cx_for_n(slot, hash_map->bucket_count) {
universe@562 368 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@562 369 while (elm != NULL) {
universe@562 370 struct cx_hash_map_element_s *next = elm->next;
universe@562 371 size_t new_slot = elm->key.hash % new_bucket_count;
universe@562 372
universe@562 373 // find position where to insert
universe@562 374 struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
universe@562 375 struct cx_hash_map_element_s *bucket_prev = NULL;
universe@562 376 while (bucket_next != NULL && bucket_next->key.hash < elm->key.hash) {
universe@562 377 bucket_prev = bucket_next;
universe@562 378 bucket_next = bucket_next->next;
universe@562 379 }
universe@562 380
universe@562 381 // insert
universe@562 382 if (bucket_prev == NULL) {
universe@562 383 elm->next = new_buckets[new_slot];
universe@562 384 new_buckets[new_slot] = elm;
universe@562 385 } else {
universe@562 386 bucket_prev->next = elm;
universe@562 387 elm->next = bucket_next;
universe@562 388 }
universe@562 389
universe@562 390 // advance
universe@562 391 elm = next;
universe@562 392 }
universe@562 393 }
universe@562 394
universe@562 395 // assign result to the map
universe@562 396 hash_map->bucket_count = new_bucket_count;
universe@562 397 cxFree(map->allocator, hash_map->buckets);
universe@562 398 hash_map->buckets = new_buckets;
universe@562 399 }
universe@562 400 return 0;
universe@562 401 }

mercurial