├── LICENSE ├── map.c ├── map.h └── readme.md /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Mashpoe 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /map.c: -------------------------------------------------------------------------------- 1 | // 2 | // map.h 3 | // 4 | // Created by Mashpoe on 1/15/21. 5 | // 6 | 7 | #include "map.h" 8 | #include 9 | #include 10 | //#include 11 | 12 | #define HASHMAP_DEFAULT_CAPACITY 20 13 | #define HASHMAP_MAX_LOAD 0.75f 14 | #define HASHMAP_RESIZE_FACTOR 2 15 | 16 | struct bucket 17 | { 18 | // `next` must be the first struct element. 19 | // changing the order will break multiple functions 20 | struct bucket* next; 21 | 22 | // key, key size, key hash, and associated value 23 | const void* key; 24 | size_t ksize; 25 | uint32_t hash; 26 | uintptr_t value; 27 | }; 28 | 29 | struct hashmap 30 | { 31 | struct bucket* buckets; 32 | int capacity; 33 | int count; 34 | 35 | #ifdef __HASHMAP_REMOVABLE 36 | // "tombstones" are empty buckets from removing elements 37 | int tombstone_count; 38 | #endif 39 | 40 | // a linked list of all valid entries, in order 41 | struct bucket* first; 42 | // lets us know where to add the next element 43 | struct bucket* last; 44 | }; 45 | 46 | hashmap* hashmap_create(void) 47 | { 48 | hashmap* m = malloc(sizeof(hashmap)); 49 | if (m == NULL) 50 | { 51 | return NULL; 52 | } 53 | 54 | m->capacity = HASHMAP_DEFAULT_CAPACITY; 55 | m->count = 0; 56 | 57 | #ifdef __HASHMAP_REMOVABLE 58 | m->tombstone_count = 0; 59 | #endif 60 | 61 | m->buckets = calloc(HASHMAP_DEFAULT_CAPACITY, sizeof(struct bucket)); 62 | if (m->buckets == NULL) { 63 | free(m); 64 | return NULL; 65 | } 66 | 67 | m->first = NULL; 68 | 69 | // this prevents branching in hashmap_set. 70 | // m->first will be treated as the "next" pointer in an imaginary bucket. 71 | // when the first item is added, m->first will be set to the correct address. 72 | m->last = (struct bucket*)&m->first; 73 | return m; 74 | } 75 | 76 | void hashmap_free(hashmap* m) 77 | { 78 | free(m->buckets); 79 | free(m); 80 | } 81 | 82 | // puts an old bucket into a resized hashmap 83 | static struct bucket* resize_entry(hashmap* m, struct bucket* old_entry) 84 | { 85 | uint32_t index = old_entry->hash % m->capacity; 86 | for (;;) 87 | { 88 | struct bucket* entry = &m->buckets[index]; 89 | 90 | if (entry->key == NULL) 91 | { 92 | *entry = *old_entry; // copy data from old entry 93 | return entry; 94 | } 95 | 96 | index = (index + 1) % m->capacity; 97 | } 98 | } 99 | 100 | static int hashmap_resize(hashmap* m) 101 | { 102 | int old_capacity = m->capacity; 103 | struct bucket* old_buckets = m->buckets; 104 | 105 | m->capacity *= HASHMAP_RESIZE_FACTOR; 106 | // initializes all bucket fields to null 107 | m->buckets = calloc(m->capacity, sizeof(struct bucket)); 108 | if (m->buckets == NULL) { 109 | m->capacity = old_capacity; 110 | m->buckets = old_buckets; 111 | return -1; 112 | } 113 | 114 | // same trick; avoids branching 115 | m->last = (struct bucket*)&m->first; 116 | 117 | #ifdef __HASHMAP_REMOVABLE 118 | m->count -= m->tombstone_count; 119 | m->tombstone_count = 0; 120 | #endif 121 | 122 | // assumes that an empty map won't be resized 123 | do 124 | { 125 | #ifdef __HASHMAP_REMOVABLE 126 | // skip entry if it's a "tombstone" 127 | struct bucket* current = m->last->next; 128 | if (current->key == NULL) 129 | { 130 | m->last->next = current->next; 131 | // skip to loop condition 132 | continue; 133 | } 134 | #endif 135 | 136 | m->last->next = resize_entry(m, m->last->next); 137 | m->last = m->last->next; 138 | } while (m->last->next != NULL); 139 | 140 | free(old_buckets); 141 | return 0; 142 | } 143 | 144 | #define HASHMAP_HASH_INIT 2166136261u 145 | 146 | // FNV-1a hash function 147 | static inline uint32_t hash_data(const unsigned char* data, size_t size) 148 | { 149 | size_t nblocks = size / 8; 150 | uint64_t hash = HASHMAP_HASH_INIT; 151 | for (size_t i = 0; i < nblocks; ++i) 152 | { 153 | hash ^= (uint64_t)data[0] << 0 | (uint64_t)data[1] << 8 | 154 | (uint64_t)data[2] << 16 | (uint64_t)data[3] << 24 | 155 | (uint64_t)data[4] << 32 | (uint64_t)data[5] << 40 | 156 | (uint64_t)data[6] << 48 | (uint64_t)data[7] << 56; 157 | hash *= 0xbf58476d1ce4e5b9; 158 | data += 8; 159 | } 160 | 161 | uint64_t last = size & 0xff; 162 | switch (size % 8) 163 | { 164 | case 7: 165 | last |= (uint64_t)data[6] << 56; /* fallthrough */ 166 | case 6: 167 | last |= (uint64_t)data[5] << 48; /* fallthrough */ 168 | case 5: 169 | last |= (uint64_t)data[4] << 40; /* fallthrough */ 170 | case 4: 171 | last |= (uint64_t)data[3] << 32; /* fallthrough */ 172 | case 3: 173 | last |= (uint64_t)data[2] << 24; /* fallthrough */ 174 | case 2: 175 | last |= (uint64_t)data[1] << 16; /* fallthrough */ 176 | case 1: 177 | last |= (uint64_t)data[0] << 8; 178 | hash ^= last; 179 | hash *= 0xd6e8feb86659fd93; 180 | } 181 | 182 | // compress to a 32-bit result. 183 | // also serves as a finalizer. 184 | return (uint32_t)(hash ^ hash >> 32); 185 | } 186 | 187 | static struct bucket* find_entry(hashmap* m, const void* key, size_t ksize, uint32_t hash) 188 | { 189 | uint32_t index = hash % m->capacity; 190 | 191 | for (;;) 192 | { 193 | struct bucket* entry = &m->buckets[index]; 194 | 195 | #ifdef __HASHMAP_REMOVABLE 196 | 197 | // compare sizes, then hashes, then key data as a last resort. 198 | // check for tombstone 199 | if ((entry->key == NULL && entry->value == 0) || 200 | // check for valid matching entry 201 | (entry->key != NULL && 202 | entry->ksize == ksize && 203 | entry->hash == hash && 204 | memcmp(entry->key, key, ksize) == 0)) 205 | { 206 | // return the entry if a match or an empty bucket is found 207 | return entry; 208 | } 209 | 210 | #else 211 | 212 | // kind of a thicc condition; 213 | // I didn't want this to span multiple if statements or functions. 214 | if (entry->key == NULL || 215 | // compare sizes, then hashes, then key data as a last resort. 216 | (entry->ksize == ksize && 217 | entry->hash == hash && 218 | memcmp(entry->key, key, ksize) == 0)) 219 | { 220 | // return the entry if a match or an empty bucket is found 221 | return entry; 222 | } 223 | #endif 224 | 225 | //printf("collision\n"); 226 | index = (index + 1) % m->capacity; 227 | } 228 | } 229 | 230 | int hashmap_set(hashmap* m, const void* key, size_t ksize, uintptr_t val) 231 | { 232 | if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { 233 | if (hashmap_resize(m) == -1) 234 | return -1; 235 | } 236 | 237 | uint32_t hash = hash_data(key, ksize); 238 | struct bucket* entry = find_entry(m, key, ksize, hash); 239 | if (entry->key == NULL) 240 | { 241 | m->last->next = entry; 242 | m->last = entry; 243 | entry->next = NULL; 244 | 245 | ++m->count; 246 | 247 | entry->key = key; 248 | entry->ksize = ksize; 249 | entry->hash = hash; 250 | } 251 | entry->value = val; 252 | return 0; 253 | } 254 | 255 | int hashmap_get_set(hashmap* m, const void* key, size_t ksize, uintptr_t* out_in) 256 | { 257 | if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { 258 | if (hashmap_resize(m) == -1) 259 | return -1; 260 | } 261 | 262 | uint32_t hash = hash_data(key, ksize); 263 | struct bucket* entry = find_entry(m, key, ksize, hash); 264 | if (entry->key == NULL) 265 | { 266 | m->last->next = entry; 267 | m->last = entry; 268 | entry->next = NULL; 269 | 270 | ++m->count; 271 | 272 | entry->value = *out_in; 273 | entry->key = key; 274 | entry->ksize = ksize; 275 | entry->hash = hash; 276 | 277 | return 0; 278 | } 279 | *out_in = entry->value; 280 | return 1; 281 | } 282 | 283 | int hashmap_set_free(hashmap* m, const void* key, size_t ksize, uintptr_t val, hashmap_callback c, void* usr) 284 | { 285 | if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { 286 | if (hashmap_resize(m) == -1) 287 | return -1; 288 | } 289 | 290 | uint32_t hash = hash_data(key, ksize); 291 | struct bucket *entry = find_entry(m, key, ksize, hash); 292 | if (entry->key == NULL) 293 | { 294 | m->last->next = entry; 295 | m->last = entry; 296 | entry->next = NULL; 297 | 298 | ++m->count; 299 | 300 | entry->key = key; 301 | entry->ksize = ksize; 302 | entry->hash = hash; 303 | entry->value = val; 304 | // there was no overwrite, exit the function. 305 | return 0; 306 | } 307 | // allow the callback to free entry data. 308 | // use old key and value so the callback can free them. 309 | // the old key and value will be overwritten after this call. 310 | int error = c(entry->key, ksize, entry->value, usr); 311 | 312 | // overwrite the old key pointer in case the callback frees it. 313 | entry->key = key; 314 | entry->value = val; 315 | return error; 316 | } 317 | 318 | int hashmap_get(hashmap* m, const void* key, size_t ksize, uintptr_t* out_val) 319 | { 320 | uint32_t hash = hash_data(key, ksize); 321 | struct bucket* entry = find_entry(m, key, ksize, hash); 322 | 323 | // if there is no match, output val will just be NULL 324 | *out_val = entry->value; 325 | 326 | return entry->key != NULL ? 1 : 0; 327 | } 328 | 329 | #ifdef __HASHMAP_REMOVABLE 330 | // doesn't "remove" the element per se, but it will be ignored. 331 | // the element will eventually be removed when the map is resized. 332 | void hashmap_remove(hashmap* m, const void* key, size_t ksize) 333 | { 334 | uint32_t hash = hash_data(key, ksize); 335 | struct bucket* entry = find_entry(m, key, ksize, hash); 336 | 337 | if (entry->key != NULL) 338 | { 339 | 340 | // "tombstone" entry is signified by a NULL key with a nonzero value 341 | // element removal is optional because of the overhead of tombstone checks 342 | entry->key = NULL; 343 | entry->value = 0xDEAD; // I mean, it's a tombstone... 344 | 345 | ++m->tombstone_count; 346 | } 347 | } 348 | 349 | void hashmap_remove_free(hashmap* m, const void* key, size_t ksize, hashmap_callback c, void* usr) 350 | { 351 | uint32_t hash = hash_data(key, ksize); 352 | struct bucket* entry = find_entry(m, key, ksize, hash); 353 | 354 | if (entry->key != NULL) 355 | { 356 | c(entry->key, entry->ksize, entry->value, usr); 357 | 358 | // "tombstone" entry is signified by a NULL key with a nonzero value 359 | // element removal is optional because of the overhead of tombstone checks 360 | entry->key = NULL; 361 | entry->value = 0xDEAD; // I mean, it's a tombstone... 362 | 363 | ++m->tombstone_count; 364 | } 365 | } 366 | #endif 367 | 368 | int hashmap_size(hashmap* m) 369 | { 370 | 371 | #ifdef __HASHMAP_REMOVABLE 372 | return m->count - m->tombstone_count; 373 | #else 374 | return m->count; 375 | #endif 376 | } 377 | 378 | int hashmap_iterate(hashmap* m, hashmap_callback c, void* user_ptr) 379 | { 380 | // loop through the linked list of valid entries 381 | // this way we can skip over empty buckets 382 | struct bucket* current = m->first; 383 | int error = 0; 384 | 385 | while (current != NULL) 386 | { 387 | #ifdef __HASHMAP_REMOVABLE 388 | // "tombstone" check 389 | if (current->key != NULL) 390 | #endif 391 | error = c(current->key, current->ksize, current->value, user_ptr); 392 | if (error == -1) 393 | break; 394 | 395 | current = current->next; 396 | } 397 | return error; 398 | } 399 | 400 | /*void bucket_dump(hashmap* m) 401 | { 402 | for (int i = 0; i < m->capacity; i++) 403 | { 404 | if (m->buckets[i].key == NULL) 405 | { 406 | if (m->buckets[i].value != 0) 407 | { 408 | printf("x"); 409 | } 410 | else 411 | { 412 | printf("0"); 413 | } 414 | } 415 | else 416 | { 417 | printf("1"); 418 | } 419 | } 420 | printf("\n"); 421 | fflush(stdout); 422 | }*/ 423 | -------------------------------------------------------------------------------- /map.h: -------------------------------------------------------------------------------- 1 | // 2 | // map.h 3 | // 4 | // Created by Mashpoe on 1/15/21. 5 | // 6 | 7 | #ifndef map_h 8 | #define map_h 9 | 10 | #define hashmap_str_lit(str) (str), sizeof(str) - 1 11 | #define hashmap_static_arr(arr) (arr), sizeof(arr) 12 | 13 | // removal of map elements is disabled by default because of its slight overhead. 14 | // if you want to enable this feature, uncomment the line below: 15 | //#define __HASHMAP_REMOVABLE 16 | 17 | #include 18 | #include 19 | 20 | // hashmaps can associate keys with pointer values or integral types. 21 | typedef struct hashmap hashmap; 22 | 23 | // a callback type used for iterating over a map/freeing entries: 24 | // `int (const void* key, size_t size, uintptr_t value, void* usr)` 25 | // `usr` is a user pointer which can be passed through `hashmap_iterate`. 26 | typedef int (*hashmap_callback)(const void *key, size_t ksize, uintptr_t value, void *usr); 27 | 28 | hashmap* hashmap_create(void); 29 | 30 | // only frees the hashmap object and buckets. 31 | // does not call free on each element's `key` or `value`. 32 | // to free data associated with an element, call `hashmap_iterate`. 33 | void hashmap_free(hashmap* map); 34 | 35 | // does not make a copy of `key`. 36 | // you must copy it yourself if you want to guarantee its lifetime, 37 | // or if you intend to call `hashmap_key_free`. 38 | // returns -1 on error. 39 | int hashmap_set(hashmap* map, const void* key, size_t ksize, uintptr_t value); 40 | 41 | // adds an entry if it doesn't exist, using the value of `*out_in`. 42 | // if it does exist, it sets value in `*out_in`, meaning the value 43 | // of the entry will be in `*out_in` regardless of whether or not 44 | // it existed in the first place. 45 | // returns -1 on error. 46 | // returns 1 if the entry already existed, returns 0 otherwise. 47 | int hashmap_get_set(hashmap* map, const void* key, size_t ksize, uintptr_t* out_in); 48 | 49 | // similar to `hashmap_set()`, but when overwriting an entry, 50 | // you'll be able properly free the old entry's data via a callback. 51 | // unlike `hashmap_set()`, this function will overwrite the original key pointer, 52 | // which means you can free the old key in the callback if applicable. 53 | int hashmap_set_free(hashmap* map, const void* key, size_t ksize, uintptr_t value, hashmap_callback c, void* usr); 54 | 55 | int hashmap_get(hashmap* map, const void* key, size_t ksize, uintptr_t* out_val); 56 | 57 | #ifdef __HASHMAP_REMOVABLE 58 | void hashmap_remove(hashmap *map, const void *key, size_t ksize); 59 | 60 | // same as `hashmap_remove()`, but it allows you to free an entry's data first via a callback. 61 | void hashmap_remove_free(hashmap* m, const void* key, size_t ksize, hashmap_callback c, void* usr); 62 | #endif 63 | 64 | int hashmap_size(hashmap* map); 65 | 66 | // iterate over the map, calling `c` on every element. 67 | // goes through elements in the order they were added. 68 | // the element's key, key size, value, and `usr` will be passed to `c`. 69 | // if `c` returns -1 the iteration is aborted. 70 | // returns the last result of `c` 71 | int hashmap_iterate(hashmap* map, hashmap_callback c, void* usr); 72 | 73 | // dumps bucket info for debugging. 74 | // allows you to see how many collisions you are getting. 75 | // `0` is an empty bucket, `1` is occupied, and `x` is removed. 76 | //void bucket_dump(hashmap *m); 77 | 78 | #endif // map_h 79 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # C HashMap 2 | A fast hash map/hash table (whatever you want to call it) for the C programming language. It can associate a key with a pointer or integer value in O(1) time. 3 | 4 | ## Docs 5 | 6 | ### Table of Contents: 7 | 8 | 1. [Create a Map](#create-a-map) 9 | 2. [Proper Usage of Keys](#proper-usage-of-keys) 10 | 3. [Getting Values From Keys](#getting-values-from-keys) 11 | 4. [Adding/Setting Entries](#addingsetting-entries) 12 | 5. [Removing Entries](#removing-entries) 13 | 6. [Callbacks/Iterating Over Elements](#callbacksiterating-over-elements) 14 | 7. [Cleaning Up](#cleaning-up) 15 | 8. [Clean up Old Data When Overwriting an Entry](#clean-up-old-data-when-overwriting-an-entry) 16 | 9. [Clean up Old Data When Removing an Entry](#clean-up-old-data-when-removing-an-entry) 17 | 18 | ## Create a Map 19 | 20 | Maps can easily be created like so: 21 | 22 | ```c 23 | hashmap* m = hashmap_create(); 24 | ``` 25 | 26 | ## Proper Usage of Keys 27 | 28 | You can use any string of bytes as a key since hashmap keys are binary-safe. This is because a user might want to hash something other than a null-terminated `char` array. 29 | 30 | Consequently, you must pass the size of the key yourself when you're setting, accessing, or removing an entry from a hashmap: 31 | 32 | ```c 33 | int error; 34 | 35 | // associates the value `400` with the key "hello" 36 | error = hashmap_set(m, "hello", sizeof("hello") - 1, 400); // `- 1` if you want to ignore the null terminator 37 | if (error == -1) 38 | fprintf(stderr, "hashmap_set: %s\n", strerror(errno)); 39 | ``` 40 | 41 | In the example above, the compiler will treat `sizeof("hello") - 1` as the constant `5`, which will have zero runtime overhead. This is an example of the freedom you get from passing in the length of a key yourself. 42 | 43 | You can use the macro `hashmap_str_lit(str)` to simplify the usage of string literal keys: 44 | 45 | ```c 46 | int error; 47 | 48 | // expands to `hashmap_set(m, "foo", 3, 400);` 49 | error = hashmap_set(m, hashmap_str_lit("foo"), 400); 50 | if (error == -1) 51 | fprintf(stderr, "hashmap_set: %s\n", strerror(errno)); 52 | /* 53 | 54 | -or- 55 | 56 | */ 57 | int error; 58 | 59 | // can also be used with static char arrays 60 | char ch_arr[] = "bar"; 61 | 62 | error = hashmap_set(m, hashmap_str_lit(ch_arr), 400); 63 | if (error == -1) 64 | fprintf(stderr, "hashmap_set: %s\n", strerror(errno)); 65 | ``` 66 | 67 | The macro `hashmap_static_arr(arr)` does the same thing, but with static arrays (or anything that's stored on the stack). The only difference is that it won't subtract from the size to account for a null terminator: 68 | 69 | ```c 70 | int error, numbers[] = {1, 2, 3, 4, 5}; 71 | 72 | // expands to `hashmap_set(m, numbers, 5, 400);` 73 | error = hashmap_set(m, hashmap_static_arr(numbers), 400); 74 | if (error == -1) 75 | fprintf(stderr, "hashmap_set: %s\n", strerror(errno)); 76 | ``` 77 | 78 | Both of these macros work for other library calls as well, such as `hashmap_get()` or `hashmap_remove()`. Just use the macro in place of the `key` and `ksize` arguments. 79 | 80 | These macros obviously won't work with pointers (unless you are using **pointer addresses** as keys), so `const char*` or `int*` arrays cannot be used in this way for example, and you must get the size some other way. 81 | 82 | For strings, the most simple solution is to use `strlen()`: 83 | 84 | ```c 85 | int error; 86 | 87 | // `get_str()` is a made-up function that returns a heap-allocated string. 88 | const char* str = get_str(); 89 | 90 | error = hashmap_set(m, str, strlen(str), 400); 91 | if (error == -1) 92 | fprintf(stderr, "hashmap_set: %s\n", strerror(errno)); 93 | ``` 94 | 95 | Unfortunatley, `strlen()` is an O(n) function, which is not ideal when you're trying to save time with hashmaps. If possible, try storing the length of your keys upon string creation for optimal performance. 96 | 97 | ### Key Lifetime 98 | 99 | Keys will not be copied when adding an entry to the hashmap. It's unsafe to free a key that is currently being used in a hashmap entry. If you need to copy a key so it will last for the entire lifetime of the hashmap, you must do that yourself (e.g. using [`strcpy()`](https://en.cppreference.com/w/c/string/byte/strcpy) or [`memcpy()`](https://en.cppreference.com/w/c/string/byte/memcpy)). If you need to free any keys that you copied over to the hashmap, read the "[Cleaning Up](#cleaning-up)" section below. 100 | 101 | ## Getting Values From Keys 102 | 103 | Use `hashmap_get()` to get the value associated with a given key: 104 | 105 | ```c 106 | // map, key data, key size, and pointer to output location 107 | int hashmap_get(hashmap* map, const void* key, size_t ksize, uintptr_t* out_val); 108 | ``` 109 | 110 | Example: 111 | 112 | ```c 113 | uintptr_t result; 114 | 115 | if (hashmap_get(m, "hello", 5, &result)) 116 | { 117 | // do something with result 118 | printf("result is %i\n", (int)result); 119 | } 120 | else 121 | { 122 | // the item could not be found 123 | printf("error: unable to locate entry \"hello\"\n"); 124 | } 125 | ``` 126 | 127 | `hashmap_get()` returns `1` if the item was found, and `0` otherwise. Internally, the hashmap stores `uintptr_t` values alongside your keys because it's an integer type that's guaranteed to be large enough to store pointer types. `uintptr_t` is defined in the C standard as an optional feature (defined in ``), but it does have pretty widespread support. 128 | 129 | In the example above, we cast the `uintptr_t` result to an integer so we can print it, but if you're using a hashmap to store pointer addresses, then you can cast it to any pointer type you like. 130 | 131 | ## Adding/Setting Entries 132 | 133 | Use `hashmap_set()` to associate a value with a given key: 134 | 135 | ```c 136 | // map, key data, key size, and associated value 137 | int hashmap_set(hashmap* map, const void* key, size_t ksize, uintptr_t value); 138 | ``` 139 | 140 | Example: 141 | 142 | ```c 143 | int x, error; 144 | 145 | error = hashmap_set(m, "hello", 5, x); 146 | if (error == -1) 147 | fprintf(stderr, "hashmap_set: %s\n", strerror(errno)); 148 | ``` 149 | 150 | `hashmap_set` will either create a new entry in the hashmap with a given key, or overwrite an existing one. 151 | 152 | ### Optional Entry Setting 153 | 154 | In some circumstances, you'll want to get the value of an entry if it exists, but set your own value if the entry doesn't exist. 155 | 156 | Normally, this would require two table lookups, but this function can do it with just one: 157 | 158 | ```c 159 | // map, key, key size, and input/output pointer 160 | int hashmap_get_set(hashmap* map, const void* key, size_t ksize, uintptr_t* out_in); 161 | ``` 162 | 163 | Example: 164 | 165 | ```c 166 | // if there is no entry, use `0` as the defalt value 167 | // if there is an entry, it's value will be stored here 168 | uintptr_t ivalue = 0; 169 | 170 | int error; 171 | 172 | // if there is a match, `ivalue` will be set to the entry's current value, 173 | // otherwise, a new entry will be created with the current value of `ivalue` 174 | error = hashmap_get_set(m, "hello", 5, &ivalue); 175 | if (error == -1) 176 | fprintf(stderr, "hashmap_get_set: %s\n", strerror(errno)); 177 | ``` 178 | 179 | ## Removing Entries 180 | 181 | Hashmaps are complicated data structures, and the removal of entries has a few side effects that require some extra overhead to deal with. 182 | 183 | While the overhead won't really slow things down by much, simply having this feature requires some extra checks because of the side effects it has on the internal memory layout of the hashmap, and these checks will have to be made regardless of whether or not you actually remove any elements. 184 | 185 | For this reason, entry removal is disabled by default. If you want to enable it, you can uncomment the `#define __HASHMAP_REMOVABLE` line at the top of `map.h`: 186 | 187 | ```c 188 | // removal of map elements is disabled by default because of the overhead. 189 | // if you want to enable this feature, uncomment the line below: 190 | //#define __HASHMAP_REMOVABLE 191 | ``` 192 | 193 | Use `hashmap_remove()` to remove entries: 194 | 195 | ```c 196 | // map, key, key size 197 | void hashmap_remove(hashmap* map, const void* key, size_t ksize); 198 | ``` 199 | 200 | Example: 201 | 202 | ```c 203 | // simply removes the entry "hello" 204 | hashmap_remove(m, "hello", 5); 205 | ``` 206 | 207 | If you want to free an entry's data before removing that entry, there's a variation of `hashmap_remove()` called `hashmap_remove_free()`. More info on this can be found in the "[Callbacks/Iterating Over Elements](#callbacksiterating-over-elements)" section. 208 | 209 | ## Callbacks/Iterating Over Elements 210 | 211 | `map.h` defines a function type for callbacks: 212 | 213 | ```c 214 | // a callback type used for iterating over a map/freeing entries: 215 | // `int (const void* key, size_t size, uintptr_t value, void* usr)` 216 | // `usr` is a user pointer which can be passed through `hashmap_iterate`. 217 | typedef int (*hashmap_callback)(const void* key, size_t ksize, uintptr_t value, void* usr); 218 | ``` 219 | 220 | These callbacks allow operations to be done on individual hashmap entries. The entry's key, key size, and value will be passed alongside a user pointer. There are multiple functions that use these callbacks, but this section will only cover one: 221 | 222 | Iterate over hashmap entries with `hashmap_iterate`: 223 | 224 | ```c 225 | // map, callback, user pointer 226 | int hashmap_iterate(hashmap* map, hashmap_callback c, void* usr); 227 | ``` 228 | 229 | Example: 230 | 231 | ```c 232 | // define our callback with the correct parameters 233 | int print_entry(const void* key, size_t ksize, uintptr_t value, void* usr) 234 | { 235 | // prints the entry's key and value 236 | // assumes the key is a null-terminated string 237 | // If there is any error with printf, the iteration will abort 238 | return printf("Entry \"%s\": %i\n", key, value); 239 | } 240 | 241 | /* 242 | ... 243 | */ 244 | 245 | int error; 246 | 247 | // print the key and value of each entry 248 | // error holds the last returned value from print_entry 249 | error = hashmap_iterate(m, print_entry, NULL); 250 | if (error == -1) 251 | fprintf(stderr, "hashmap_iterate: %s\n", strerror(errno)); 252 | ``` 253 | 254 | `hashmap_iterate` will iterate over each element in the order they were originally added. 255 | If the callback returns `-1` then then iteration is aborted. 256 | `hashmap_iterate` returns the last callback result. 257 | 258 | Internally, all entries are stored in one continuous chunk of memory alongside empty "buckets," which are reserved space for entries that may need to be set in that location. All entries double as a linked-list, which is done by keeping track of the most recent entry so it can be linked to the next entry once it's added. 259 | 260 | When the map reaches a certain capacity, it needs to be resized, and the valid entries must be copied to a new chunk of memory. The linked list structure makes this process more efficient because it allows the hashmap to quickly iterate through all valid entries rather than having to check each "bucket" for a valid entry to copy over. 261 | 262 | A side effect of using a linked list structure is that it also allows us to iterate over a hashmap's entries in the order they were originally added, and this can be done at no extra cost! 263 | 264 | ## Cleaning Up 265 | 266 | You can free a hashmap's internal data with `hashmap_free()`: 267 | 268 | ```c 269 | void hashmap_free(hashmap* map); 270 | ``` 271 | 272 | The hashmap does not make copies of the keys that you provide, so make sure you free them properly. If you want to rely solely on the hashmap to do this, then you can use [`hashmap_iterate()`](#callbacksiterating-over-elements) to free each key. If you want to free an entry's key and/or value before removing the entry, you can call [`hashmap_remove_free()`](#clean-up-old-data-when-removing-an-entry). If you want to free an entry's key and/or value before overwriting the entry, you can call [`hashmap_set_free()`](#clean-up-old-data-when-overwriting-an-entry). 273 | 274 | This also applies to freeing any data that's referenced by an entry's `uintptr_t` value. 275 | 276 | Freeing keys through `hashmap_iterate()` can be safely done before calling `hashmap_free()`, but doing anything besides freeing the hashmap after freeing or modifying keys is unsafe. 277 | 278 | More information on `hashmap_iterate()` can be found in the "[Callbacks/Iterating Over Elements](#callbacksiterating-over-elements)" section above. 279 | 280 | ### Clean up Old Data When Overwriting an Entry 281 | 282 | You can use a callback to free an entry's data before overwriting it: 283 | 284 | ```c 285 | // map, key, key size, new value, callback, user pointer 286 | void hashmap_set_free(hashmap* map, const void* key, size_t ksize, uintptr_t value, hashmap_callback c, void* usr); 287 | ``` 288 | 289 | `hashmap_set_free()` is similar to `hashmap_set()`, but when overwriting an entry, you'll be able properly free the old entry's data via a callback. Unlike `hashmap_set()`, this function will overwrite the original key pointer, which means you can free the old key in the callback if applicable. 290 | 291 | Example using `hashmap_set_free()`: 292 | 293 | ```c 294 | // user defined function 295 | int ov_free(const void* key, size_t ksize, uintptr_t value, void* usr) 296 | { 297 | free((void*)value); 298 | 299 | // free the old key if it has a different address 300 | if (key != usr) 301 | { 302 | free((void*)key); 303 | } 304 | 305 | // Continue iterating 306 | return 0; 307 | } 308 | 309 | /* 310 | ... 311 | */ 312 | 313 | // `get_str()` is a made-up function that returns a heap-allocated string. 314 | const char* key = get_str(); 315 | 316 | // we'll pass the key as the user pointer so it's address can be compared to the old key's address. 317 | hashmap_set_free(m, key, strlen(key), 400, ov_free, key); 318 | ``` 319 | 320 | More information on using callbacks can be found in the "[Callbacks/Iterating Over Elements](#callbacksiterating-over-elements)" section above. 321 | 322 | ### Clean up Old Data When Removing an Entry 323 | 324 | You can use a callback to free an entry's data before removing it. You don't necessarily *have* to use this callback to free data, but that's its primary function. 325 | 326 | This can be done using `hashmap_remove_free()`: 327 | 328 | ```c 329 | void hashmap_remove_free(hashmap* m, const void* key, size_t ksize, hashmap_callback* c, void* usr); 330 | ``` 331 | 332 | **NOTE:** Before using this function, be sure to read the "[Removing Entries](#removing-entries)" section above. 333 | 334 | Example: 335 | 336 | ```c 337 | // user defined function 338 | int free_map_entry(const void* key, size_t ksize, uintptr_t value, void* usr) 339 | { 340 | free((void*)value); 341 | return 0; 342 | } 343 | 344 | /* 345 | ... 346 | */ 347 | int error; 348 | 349 | // the hashmap now owns the allocated space 350 | error = hashmap_set(m, "hello", 5, malloc(100)); 351 | 352 | /* 353 | ... 354 | */ 355 | 356 | // free the entry's data before removing the entry 357 | if (error != -1) 358 | hashmap_remove_free(m, "hello", 5, free_map_entry, NULL); 359 | ``` 360 | 361 | More information on using callbacks can be found in the "[Callbacks/Iterating Over Elements](#callbacksiterating-over-elements)" section above. 362 | --------------------------------------------------------------------------------