src/hash_map.c

Thu, 19 May 2022 14:30:20 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 19 May 2022 14:30:20 +0200
changeset 550
89b2a83728b1
parent 549
d7f0b5a9a985
child 551
2946e13c89a4
permissions
-rw-r--r--

#189 basic map implementation

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@549 178 /**
universe@550 179 * Helper function to avoid code duplication.
universe@549 180 *
universe@550 181 * @param map the map
universe@550 182 * @param key the key to look up
universe@550 183 * @param remove flag indicating whether the looked up entry shall be removed
universe@550 184 * @return the value corresponding to the key or \c NULL
universe@549 185 */
universe@550 186 static void *cx_hash_map_get_remove(
universe@550 187 CxMap *map,
universe@550 188 CxDataPtr key,
universe@550 189 bool remove
universe@550 190 ) {
universe@550 191 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
universe@550 192
universe@550 193 unsigned hash = cx_hash_map_murmur(key.data, key.len);
universe@550 194
universe@550 195 size_t slot = hash % hash_map->bucket_count;
universe@550 196 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
universe@550 197 struct cx_hash_map_element_s *prev = NULL;
universe@550 198 while (elm && elm->key.hash <= hash) {
universe@550 199 if (elm->key.hash == hash && elm->key.len == key.len) {
universe@550 200 if (memcmp(elm->key.data, key.data, key.len) == 0) {
universe@550 201 void *data = elm->data;
universe@550 202 if (remove) {
universe@550 203 // unlink
universe@550 204 if (prev) {
universe@550 205 prev->next = elm->next;
universe@550 206 } else {
universe@550 207 hash_map->buckets[slot] = elm->next;
universe@550 208 }
universe@550 209 // free element
universe@550 210 cxFree(map->allocator, elm->key.data);
universe@550 211 cxFree(map->allocator, elm);
universe@550 212 // decrease size
universe@550 213 map->size--;
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@550 227 CxDataPtr key
universe@550 228 ) {
universe@550 229 // we can safely cast, because we know when remove=false, the map stays untouched
universe@550 230 return cx_hash_map_get_remove((CxMap *) map, key, false);
universe@550 231 }
universe@550 232
universe@550 233 static void *cx_hash_map_remove(
universe@550 234 CxMap *map,
universe@550 235 CxDataPtr key
universe@550 236 ) {
universe@550 237 return cx_hash_map_get_remove(map, key, true);
universe@550 238 }
universe@550 239
universe@550 240 static CxIterator cx_hash_map_iterator(CxMap const *map) {
universe@550 241 CxIterator iter;
universe@550 242 // TODO: initialize iter
universe@550 243 return iter;
universe@550 244 }
universe@550 245
universe@550 246 static CxIterator cx_hash_map_iterator_keys(CxMap const *map) {
universe@550 247 CxIterator iter;
universe@550 248 // TODO: initialize iter
universe@550 249 return iter;
universe@550 250 }
universe@550 251
universe@550 252 static CxIterator cx_hash_map_iterator_values(CxMap const *map) {
universe@550 253 CxIterator iter;
universe@550 254 // TODO: initialize iter
universe@550 255 return iter;
universe@550 256 }
universe@550 257
universe@550 258 static cx_map_class cx_hash_map_class = {
universe@550 259 cx_hash_map_destructor,
universe@550 260 cx_hash_map_clear,
universe@550 261 cx_hash_map_put,
universe@550 262 cx_hash_map_get,
universe@550 263 cx_hash_map_remove,
universe@550 264 cx_hash_map_iterator,
universe@550 265 cx_hash_map_iterator_keys,
universe@550 266 cx_hash_map_iterator_values,
universe@550 267 };
universe@550 268
universe@550 269 CxMap *cxHashMapCreate(
universe@550 270 CxAllocator *allocator,
universe@550 271 size_t buckets
universe@550 272 ) {
universe@550 273 if (buckets == 0) {
universe@550 274 // implementation defined default
universe@550 275 buckets = 16;
universe@550 276 }
universe@550 277
universe@550 278 struct cx_hash_map_s *map = cxMalloc(allocator, sizeof(struct cx_hash_map_s));
universe@550 279 if (map == NULL) return NULL;
universe@550 280
universe@550 281 // initialize hash map members
universe@550 282 map->bucket_count = buckets;
universe@550 283 map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *));
universe@550 284 if (map->buckets == NULL) {
universe@550 285 cxFree(allocator, map);
universe@550 286 return NULL;
universe@550 287 }
universe@550 288
universe@550 289 // initialize base members
universe@550 290 map->base.cl = &cx_hash_map_class;
universe@550 291 map->base.allocator = allocator;
universe@550 292 map->base.size = 0;
universe@550 293
universe@550 294 return (CxMap *) map;
universe@550 295 }

mercurial