src/hash_map.c

Sat, 21 May 2022 11:22:47 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 21 May 2022 11:22:47 +0200
changeset 551
2946e13c89a4
parent 550
89b2a83728b1
child 554
fd3d843b839d
permissions
-rw-r--r--

#189 implement map iterators

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@550 29 #include <errno.h>
universe@549 30 #include <string.h>
universe@549 31 #include "cx/hash_map.h"
universe@550 32 #include "cx/utils.h"
universe@549 33
universe@549 34 /**
universe@549 35 * Computes a murmur hash-2.
universe@549 36 *
universe@549 37 * @param data the data to hash
universe@549 38 * @param len the length of the data
universe@549 39 * @return the murmur hash-2 of the data
universe@549 40 */
universe@549 41 static unsigned cx_hash_map_murmur(
universe@549 42 unsigned char const *data,
universe@549 43 size_t len
universe@549 44 ) {
universe@549 45 unsigned m = 0x5bd1e995;
universe@549 46 unsigned r = 24;
universe@549 47 unsigned h = 25 ^ len;
universe@549 48 unsigned i = 0;
universe@549 49 while (len >= 4) {
universe@549 50 unsigned k = data[i + 0] & 0xFF;
universe@549 51 k |= (data[i + 1] & 0xFF) << 8;
universe@549 52 k |= (data[i + 2] & 0xFF) << 16;
universe@549 53 k |= (data[i + 3] & 0xFF) << 24;
universe@549 54
universe@549 55 k *= m;
universe@549 56 k ^= k >> r;
universe@549 57 k *= m;
universe@549 58
universe@549 59 h *= m;
universe@549 60 h ^= k;
universe@549 61
universe@549 62 i += 4;
universe@549 63 len -= 4;
universe@549 64 }
universe@549 65
universe@549 66 switch (len) {
universe@549 67 case 3:
universe@549 68 h ^= (data[i + 2] & 0xFF) << 16;
universe@549 69 __attribute__((__fallthrough__));
universe@549 70 case 2:
universe@549 71 h ^= (data[i + 1] & 0xFF) << 8;
universe@549 72 __attribute__((__fallthrough__));
universe@549 73 case 1:
universe@549 74 h ^= (data[i + 0] & 0xFF);
universe@549 75 h *= m;
universe@549 76 __attribute__((__fallthrough__));
universe@549 77 default:
universe@549 78 /* do nothing */;
universe@549 79 }
universe@549 80
universe@549 81 h ^= h >> 13;
universe@549 82 h *= m;
universe@549 83 h ^= h >> 15;
universe@549 84
universe@549 85 return h;
universe@549 86 }
universe@549 87
universe@550 88 static void cx_hash_map_clear(struct cx_map_s *map) {
universe@550 89 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 90 cx_for_n(i, hash_map->bucket_count) {
universe@550 91 struct cx_hash_map_element_s *elem = hash_map->buckets[i];
universe@550 92 if (elem != NULL) {
universe@550 93 do {
universe@550 94 struct cx_hash_map_element_s *next = elem->next;
universe@550 95 // free the key data
universe@550 96 cxFree(map->allocator, elem->key.data);
universe@550 97 // free the node
universe@550 98 cxFree(map->allocator, elem);
universe@550 99 // proceed
universe@550 100 elem = next;
universe@550 101 } while (elem != NULL);
universe@550 102
universe@550 103 // do not leave a dangling pointer
universe@550 104 hash_map->buckets[i] = NULL;
universe@550 105 }
universe@550 106 }
universe@550 107 map->size = 0;
universe@550 108 }
universe@550 109
universe@550 110 static void cx_hash_map_destructor(struct cx_map_s *map) {
universe@550 111 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 112
universe@550 113 // free the buckets
universe@550 114 cx_hash_map_clear(map);
universe@550 115 cxFree(map->allocator, hash_map->buckets);
universe@550 116
universe@550 117 // free the map structure
universe@550 118 cxFree(map->allocator, map);
universe@550 119 }
universe@550 120
universe@550 121 static int cx_hash_map_put(
universe@550 122 CxMap *map,
universe@550 123 CxDataPtr key,
universe@550 124 void *value
universe@550 125 ) {
universe@550 126 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 127 CxAllocator *allocator = map->allocator;
universe@550 128
universe@550 129 unsigned hash = cx_hash_map_murmur(key.data, key.len);
universe@550 130
universe@550 131 size_t slot = hash % hash_map->bucket_count;
universe@550 132 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 133 struct cx_hash_map_element_s *prev = NULL;
universe@550 134
universe@550 135 while (elm != NULL && elm->key.hash < hash) {
universe@550 136 prev = elm;
universe@550 137 elm = elm->next;
universe@550 138 }
universe@550 139
universe@550 140 if (elm == NULL || elm->key.hash != hash) {
universe@550 141 struct cx_hash_map_element_s *e = cxMalloc(allocator, sizeof(struct cx_hash_map_element_s));
universe@550 142 if (e == NULL) {
universe@550 143 return -1;
universe@550 144 }
universe@550 145
universe@550 146 // write the value
universe@550 147 // TODO: depending on future map features, we may want to copy here
universe@550 148 e->data = value;
universe@550 149
universe@550 150 // copy the key
universe@550 151 void *kd = cxMalloc(allocator, key.len);
universe@550 152 if (kd == NULL) {
universe@550 153 return -1;
universe@550 154 }
universe@550 155 memcpy(kd, key.data, key.len);
universe@550 156 e->key.data = kd;
universe@550 157 e->key.len = key.len;
universe@550 158 e->key.hash = hash;
universe@550 159
universe@550 160 // insert the element into the linked list
universe@550 161 if (prev == NULL) {
universe@550 162 hash_map->buckets[slot] = e;
universe@550 163 } else {
universe@550 164 prev->next = e;
universe@550 165 }
universe@550 166 e->next = elm;
universe@550 167
universe@550 168 // increase the size
universe@550 169 map->size++;
universe@550 170 } else {
universe@550 171 // (elem != NULL && elem->key.hash == hash) - overwrite value of existing element
universe@550 172 elm->data = value;
universe@550 173 }
universe@550 174
universe@550 175 return 0;
universe@550 176 }
universe@549 177
universe@551 178 static void cx_hash_map_unlink(
universe@551 179 struct cx_hash_map_s *hash_map,
universe@551 180 size_t slot,
universe@551 181 struct cx_hash_map_element_s *prev,
universe@551 182 struct cx_hash_map_element_s *elm
universe@551 183 ) {
universe@551 184 // unlink
universe@551 185 if (prev == NULL) {
universe@551 186 hash_map->buckets[slot] = elm->next;
universe@551 187 } else {
universe@551 188 prev->next = elm->next;
universe@551 189 }
universe@551 190 // free element
universe@551 191 cxFree(hash_map->base.allocator, elm->key.data);
universe@551 192 cxFree(hash_map->base.allocator, elm);
universe@551 193 // decrease size
universe@551 194 hash_map->base.size--;
universe@551 195 }
universe@551 196
universe@549 197 /**
universe@550 198 * Helper function to avoid code duplication.
universe@549 199 *
universe@550 200 * @param map the map
universe@550 201 * @param key the key to look up
universe@550 202 * @param remove flag indicating whether the looked up entry shall be removed
universe@550 203 * @return the value corresponding to the key or \c NULL
universe@549 204 */
universe@550 205 static void *cx_hash_map_get_remove(
universe@550 206 CxMap *map,
universe@550 207 CxDataPtr key,
universe@550 208 bool remove
universe@550 209 ) {
universe@550 210 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 211
universe@550 212 unsigned hash = cx_hash_map_murmur(key.data, key.len);
universe@550 213
universe@550 214 size_t slot = hash % hash_map->bucket_count;
universe@550 215 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 216 struct cx_hash_map_element_s *prev = NULL;
universe@550 217 while (elm && elm->key.hash <= hash) {
universe@550 218 if (elm->key.hash == hash && elm->key.len == key.len) {
universe@550 219 if (memcmp(elm->key.data, key.data, key.len) == 0) {
universe@550 220 void *data = elm->data;
universe@550 221 if (remove) {
universe@551 222 cx_hash_map_unlink(hash_map, slot, prev, elm);
universe@550 223 }
universe@550 224 return data;
universe@550 225 }
universe@550 226 }
universe@550 227 prev = elm;
universe@550 228 elm = prev->next;
universe@550 229 }
universe@550 230
universe@550 231 return NULL;
universe@549 232 }
universe@550 233
universe@550 234 static void *cx_hash_map_get(
universe@550 235 CxMap const *map,
universe@550 236 CxDataPtr key
universe@550 237 ) {
universe@550 238 // we can safely cast, because we know when remove=false, the map stays untouched
universe@550 239 return cx_hash_map_get_remove((CxMap *) map, key, false);
universe@550 240 }
universe@550 241
universe@550 242 static void *cx_hash_map_remove(
universe@550 243 CxMap *map,
universe@550 244 CxDataPtr key
universe@550 245 ) {
universe@550 246 return cx_hash_map_get_remove(map, key, true);
universe@550 247 }
universe@550 248
universe@551 249 static void *cx_hash_map_iter_current_entry(CxIterator const *iter) {
universe@551 250 // struct has to have a compatible signature
universe@551 251 struct cx_map_entry_s *entry = (struct cx_map_entry_s *) &(iter->kv_data);
universe@551 252 return entry;
universe@551 253 }
universe@551 254
universe@551 255 static void *cx_hash_map_iter_current_key(CxIterator const *iter) {
universe@551 256 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 257 return &elm->key;
universe@551 258 }
universe@551 259
universe@551 260 static void *cx_hash_map_iter_current_value(CxIterator const *iter) {
universe@551 261 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 262 // TODO: return a pointer to data if this map is storing copies
universe@551 263 return elm->data;
universe@551 264 }
universe@551 265
universe@551 266 static bool cx_hash_map_iter_valid(CxIterator const *iter) {
universe@551 267 return iter->elem_handle != NULL;
universe@551 268 }
universe@551 269
universe@551 270 static void cx_hash_map_iter_next(CxIterator *iter) {
universe@551 271 struct cx_hash_map_s *map = iter->src_handle;
universe@551 272 struct cx_hash_map_element_s *elm = iter->elem_handle;
universe@551 273
universe@551 274 // remove current element, if asked
universe@551 275 if (iter->remove) {
universe@551 276 // clear the flag
universe@551 277 iter->remove = false;
universe@551 278
universe@551 279 // determine the next element
universe@551 280 struct cx_hash_map_element_s *next = elm->next;
universe@551 281
universe@551 282 // search the previous element
universe@551 283 struct cx_hash_map_element_s *prev = NULL;
universe@551 284 if (map->buckets[iter->slot] != elm) {
universe@551 285 prev = map->buckets[iter->slot];
universe@551 286 while (prev->next != elm) {
universe@551 287 prev = prev->next;
universe@551 288 }
universe@551 289 }
universe@551 290
universe@551 291 // unlink
universe@551 292 cx_hash_map_unlink(map, iter->slot, prev, elm);
universe@551 293
universe@551 294 // advance
universe@551 295 elm = next;
universe@551 296 } else {
universe@551 297 // just advance
universe@551 298 elm = elm->next;
universe@551 299 }
universe@551 300
universe@551 301 // do we leave the bucket?
universe@551 302 if (elm == NULL) {
universe@551 303 // search the next bucket
universe@551 304 for (; elm == NULL && iter->slot < map->bucket_count; iter->slot++) {
universe@551 305 elm = map->buckets[iter->slot];
universe@551 306 }
universe@551 307 }
universe@551 308
universe@551 309 // advance the index in any case
universe@551 310 iter->index++;
universe@551 311
universe@551 312 // fill the struct with the current 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@551 319 // TODO: pointer to data if this map is storing copies
universe@551 320 iter->kv_data.value = elm->data;
universe@551 321 }
universe@551 322 }
universe@551 323
universe@551 324 static CxIterator cx_hash_map_iterator(CxMap *map) {
universe@550 325 CxIterator iter;
universe@551 326
universe@551 327 iter.src_handle = map;
universe@551 328 iter.valid = cx_hash_map_iter_valid;
universe@551 329 iter.next = cx_hash_map_iter_next;
universe@551 330 iter.current = cx_hash_map_iter_current_entry;
universe@551 331
universe@551 332 iter.slot = 0;
universe@551 333 iter.index = 0;
universe@551 334 iter.remove = false;
universe@551 335
universe@551 336 if (map->size > 0) {
universe@551 337 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@551 338 struct cx_hash_map_element_s *elem = NULL;
universe@551 339 for (; elem == NULL; iter.slot++) {
universe@551 340 elem = hash_map->buckets[iter.slot];
universe@551 341 }
universe@551 342 iter.elem_handle = elem;
universe@551 343 iter.kv_data.key = NULL;
universe@551 344 iter.kv_data.value = NULL;
universe@551 345 } else {
universe@551 346 iter.elem_handle = NULL;
universe@551 347 iter.kv_data.key = NULL;
universe@551 348 iter.kv_data.value = NULL;
universe@551 349 }
universe@551 350
universe@550 351 return iter;
universe@550 352 }
universe@550 353
universe@551 354 static CxIterator cx_hash_map_iterator_keys(CxMap *map) {
universe@551 355 CxIterator iter = cx_hash_map_iterator(map);
universe@551 356 iter.current = cx_hash_map_iter_current_key;
universe@550 357 return iter;
universe@550 358 }
universe@550 359
universe@551 360 static CxIterator cx_hash_map_iterator_values(CxMap *map) {
universe@551 361 CxIterator iter = cx_hash_map_iterator(map);
universe@551 362 iter.current = cx_hash_map_iter_current_value;
universe@550 363 return iter;
universe@550 364 }
universe@550 365
universe@550 366 static cx_map_class cx_hash_map_class = {
universe@550 367 cx_hash_map_destructor,
universe@550 368 cx_hash_map_clear,
universe@550 369 cx_hash_map_put,
universe@550 370 cx_hash_map_get,
universe@550 371 cx_hash_map_remove,
universe@550 372 cx_hash_map_iterator,
universe@550 373 cx_hash_map_iterator_keys,
universe@550 374 cx_hash_map_iterator_values,
universe@550 375 };
universe@550 376
universe@550 377 CxMap *cxHashMapCreate(
universe@550 378 CxAllocator *allocator,
universe@550 379 size_t buckets
universe@550 380 ) {
universe@550 381 if (buckets == 0) {
universe@550 382 // implementation defined default
universe@550 383 buckets = 16;
universe@550 384 }
universe@550 385
universe@550 386 struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s));
universe@550 387 if (map == NULL) return NULL;
universe@550 388
universe@550 389 // initialize hash map members
universe@550 390 map->bucket_count = buckets;
universe@550 391 map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *));
universe@550 392 if (map->buckets == NULL) {
universe@550 393 cxFree(allocator, map);
universe@550 394 return NULL;
universe@550 395 }
universe@550 396
universe@550 397 // initialize base members
universe@550 398 map->base.cl = &cx_hash_map_class;
universe@550 399 map->base.allocator = allocator;
universe@550 400 map->base.size = 0;
universe@550 401
universe@550 402 return (CxMap *) map;
universe@550 403 }

mercurial