agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafs| Log | Files | Refs | README | LICENSE | Mail | Website |
hashmap.c (9004B)
1 // Taken from https://github.com/prismz/hashmap
2 #include "hashmap.h"
3
4 #include "arena.h"
5 #include <assert.h>
6 #include <inttypes.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #ifdef HM_DEBUG
13 #include <stdarg.h>
14 static void
15 debug_print(char *fmt, ...)
16 {
17 va_list ap;
18 va_start(ap, fmt);
19 vprintf(fmt, ap);
20 va_end(ap);
21 }
22 #else
23 /* compiler should just optimize this away */
24 static void
25 debug_print()
26 {
27 }
28 #endif
29
30 static int
31 hm_check_mem(void *ptr)
32 {
33 if (ptr != NULL)
34 return 0;
35
36 if (HM_EXIT_ON_ALLOC_FAIL) {
37 fprintf(stderr, "failed to allocate memory\n");
38 exit(1);
39 }
40
41 return 1;
42 }
43
44 /* hash a single byte */
45 static uint32_t
46 fnv1a(unsigned char byte, uint32_t hash)
47 {
48 return (byte ^ hash) * HM_FNV_PRIME;
49 }
50
51 static uint32_t
52 fnv1a_hash(const char *str)
53 {
54 uint32_t hash = HM_FNV_SEED;
55 while (*str)
56 hash = fnv1a((unsigned char)*str++, hash);
57 return hash;
58 }
59
60 /* will duplicate key */
61 static struct bucket *
62 new_bucket(const char *key, void *val)
63 {
64 if (key == NULL)
65 return NULL;
66
67 struct bucket *b = HM_CALLOC_FUNC(1, sizeof(struct bucket));
68 if (hm_check_mem(b))
69 return NULL;
70
71 b->key = HM_STRDUP_FUNC(key);
72 if (hm_check_mem(b->key)) {
73 free(b);
74 return NULL;
75 }
76
77 b->val = val;
78 b->next = NULL;
79
80 b->hash = fnv1a_hash(key);
81
82 debug_print("creating bucket with key %s, hash of this key is %" PRIu32
83 "\n",
84 key, b->hash);
85
86 return b;
87 }
88
89 struct hashmap *
90 hashmap_new(void (*val_free_func)(void *))
91 {
92 /* Any smaller and we'd pretty much instantly have to resize */
93 size_t capacity = HM_DEFAULT_HASHMAP_SIZE;
94
95 struct hashmap *hm = HM_CALLOC_FUNC(1, sizeof(struct hashmap));
96 if (hm_check_mem(hm))
97 return NULL;
98
99 hm->capacity = capacity;
100 hm->n_buckets = 0;
101 hm->val_free_func = val_free_func;
102 hm->buckets = HM_CALLOC_FUNC(capacity, sizeof(struct bucket *));
103 if (hm_check_mem(hm->buckets)) {
104 free(hm);
105 return NULL;
106 }
107
108 return hm;
109 }
110
111 static void
112 free_bucket(struct bucket *b, void (*val_free_func)(void *))
113 {
114 if (b == NULL)
115 return;
116
117 struct bucket *curr;
118 while (b != NULL) {
119 curr = b;
120 b = b->next;
121
122 free(curr->key);
123 if (val_free_func != NULL && curr->val != NULL)
124 val_free_func(curr->val);
125 free(curr);
126 }
127 }
128
129 void
130 hashmap_free(struct hashmap *hm)
131 {
132 if (hm == NULL)
133 return;
134 for (size_t i = 0; i < hm->capacity; i++) {
135 if (hm->buckets[i] == NULL)
136 continue;
137 free_bucket(hm->buckets[i], hm->val_free_func);
138 }
139 free(hm->buckets);
140 free(hm);
141 }
142
143 int
144 hashmap_resize(struct hashmap *hm)
145 {
146 if (hm == NULL)
147 return -1;
148
149 size_t new_size = hm->capacity * HM_RESIZE_SCALE_FACTOR;
150
151 struct bucket **buckets =
152 HM_CALLOC_FUNC(new_size, sizeof(struct bucket *));
153 if (hm_check_mem(buckets))
154 return 1;
155
156 for (size_t i = 0; i < hm->capacity; i++) {
157 if (hm->buckets[i] == NULL)
158 continue;
159
160 struct bucket *target = hm->buckets[i];
161 struct bucket *curr;
162 while (target != NULL) {
163 curr = target;
164 target = target->next;
165
166 size_t curr_new_idx = curr->hash % (uint32_t)(new_size);
167
168 if (buckets[curr_new_idx] == NULL) {
169 buckets[curr_new_idx] = curr;
170 buckets[curr_new_idx]->next = NULL;
171 continue;
172 }
173
174 /* collision */
175
176 struct bucket *ptr = buckets[curr_new_idx];
177 while (ptr->next != NULL)
178 ptr = ptr->next;
179
180 ptr->next = curr;
181 ptr->next->next = NULL;
182 }
183
184 /* size_t new_idx = hm->buckets[i]->hash % (uint32_t)new_size;
185 debug_print("buckets at idx %ld move to idx %ld\n", i, new_idx);
186 if (buckets[new_idx] != NULL) {
187 free(buckets);
188 return 1;
189 }
190 buckets[new_idx] = hm->buckets[i]; */
191 }
192
193 free(hm->buckets);
194 hm->buckets = buckets;
195 hm->capacity = new_size;
196
197 return 0;
198 }
199
200 int
201 hashmap_insert(struct hashmap *hm, const char *key, void *val)
202 {
203 if (hm == NULL || key == NULL)
204 return 1;
205
206 if ((float)(hm->n_buckets + 1) >
207 (HM_HASHMAP_MAX_LOAD * (float)hm->capacity)) {
208 if (hashmap_resize(hm))
209 return -1;
210 }
211
212 hm->n_buckets++;
213 struct bucket *b = new_bucket(key, val);
214 if (hm_check_mem(b))
215 return 1;
216 size_t idx = (b->hash) % (uint32_t)(hm->capacity);
217
218 struct bucket *target_bucket = hm->buckets[idx];
219 if (target_bucket == NULL) {
220 debug_print("insert: bucket with key %s goes to idx %ld\n",
221 b->key, idx);
222 hm->buckets[idx] = b;
223 return 0;
224 }
225
226 /* changing value of something already in the hashmap */
227 struct bucket *curr;
228 while (target_bucket != NULL) {
229 curr = target_bucket;
230 target_bucket = target_bucket->next;
231 if (curr->hash == b->hash && strcmp(curr->key, key) == 0) {
232 debug_print("key already exists, reassigning value\n");
233
234 /* we free the old value, should probably add an
235 * option for this */
236 if (hm->val_free_func != NULL)
237 hm->val_free_func(curr->val);
238 curr->val = val;
239 free_bucket(b, NULL);
240 return 0;
241 }
242 }
243
244 debug_print("collision. appending to linked list\n");
245
246 target_bucket = hm->buckets[idx];
247
248 while (target_bucket->next != NULL)
249 target_bucket = target_bucket->next;
250
251 target_bucket->next = b;
252 b->next = NULL;
253
254 return 0;
255 }
256
257 void *
258 hashmap_get(struct hashmap *hm, const char *key)
259 {
260 if (hm == NULL || key == NULL)
261 return NULL;
262
263 uint32_t hash = fnv1a_hash(key);
264 size_t idx = hash % (uint32_t)(hm->capacity);
265 debug_print("retreiving object with key %s - hashes to %" PRIu32
266 " giving idx %ld\n",
267 key, hash, idx);
268
269 struct bucket *target_bucket = hm->buckets[idx];
270 if (target_bucket == NULL)
271 return NULL;
272
273 if (target_bucket->hash == hash && strcmp(target_bucket->key, key) == 0)
274 return target_bucket->val;
275
276 /* collision */
277 struct bucket *curr;
278 while (target_bucket != NULL) {
279 curr = target_bucket;
280 target_bucket = target_bucket->next;
281 if (curr->hash == hash && strcmp(curr->key, key) == 0)
282 return curr->val;
283 }
284
285 #ifdef HM_DONTTRUSTHASH
286 /* search all items as a last resort */
287 for (size_t i = 0; i < hm->capacity; i++) {
288 struct bucket *b = hm->buckets[i];
289 struct bucket *curr;
290 while (b != NULL) {
291 curr = b;
292 b = b->next;
293
294 if (curr->hash == hash && strcmp(curr->key, key) == 0)
295 return curr->val;
296 }
297 }
298 #endif
299
300 return NULL;
301 }
302
303 /*
304 * wrapper function, so that if we don't find it with normal
305 * methods, we can easily re-call the function with a custom index, allowing
306 * us to iterate through the hashmap to find the item if for some
307 * reason the hash gives an incorrect index
308 */
309 static int
310 _hashmap_remove(struct hashmap *hm, const char *key, uint32_t hash, size_t idx)
311 {
312 if (hm == NULL || key == NULL)
313 return 1;
314
315 struct bucket *target = hm->buckets[idx];
316 struct bucket *prev = NULL;
317
318 while (target != NULL) {
319 if (target->hash == hash && strcmp(target->key, key) == 0) {
320 if (prev == NULL) {
321 // Removing first node in bucket chain
322 hm->buckets[idx] = target->next;
323 }
324 else {
325 prev->next = target->next;
326 }
327 target->next = NULL;
328 free_bucket(target, hm->val_free_func);
329 hm->n_buckets--;
330 return 0;
331 }
332 prev = target;
333 target = target->next;
334 }
335 return 1;
336 }
337
338 int
339 hashmap_remove(struct hashmap *hm, const char *key)
340 {
341 if (hm == NULL || key == NULL)
342 return 1;
343
344 uint32_t hash = fnv1a_hash(key);
345 size_t idx = hash % (uint32_t)(hm->capacity);
346
347 if (_hashmap_remove(hm, key, hash, idx) == 0)
348 return 0;
349
350 #ifdef HM_DONTTRUSTHASH
351 debug_print("couldn't find item to remove by hashing, must search "
352 "entire hashmap\n");
353 for (size_t i = 0; i < hm->capacity; i++) {
354 /* skip if we already processed the index or if the item is NULL
355 */
356 if (hm->buckets[i] == NULL || i == idx)
357 continue;
358
359 if (_hashmap_remove(hm, key, hash, i) == 0) {
360 debug_print(
361 "successfully found and removed item at idx %ld\n",
362 i);
363 return 0;
364 }
365 }
366
367 debug_print("couldn't find item to remove anywhere, returning 1\n");
368 #endif
369
370 return 1;
371 }
372
373 /* only works for hashmap with strings as values */
374 void
375 hashmap_print(struct hashmap *hm)
376 {
377 if (hm == NULL) {
378 printf("NULL\n");
379 return;
380 }
381
382 printf("Printing hashmap containing %ld buckets with capacity %ld:\n",
383 hm->n_buckets, hm->capacity);
384
385 for (size_t i = 0; i < hm->capacity; i++) {
386 struct bucket *target = hm->buckets[i];
387 printf("bucket %02ld:\n", i);
388 if (target == NULL)
389 continue;
390
391 struct bucket *curr;
392 int n = 0;
393 while (target != NULL) {
394 curr = target;
395 target = target->next;
396 printf(" %02d: %s=%s\n", n, curr->key,
397 (char *)curr->val);
398
399 n++;
400 }
401 }
402 }
403
404 char **
405 hashmap_get_keys(struct hashmap *hm, size_t *n_keys)
406 {
407 if (!hm || !n_keys)
408 return NULL;
409
410 char **keys = malloc(hm->n_buckets * sizeof(char *));
411 if (!keys)
412 return NULL;
413
414 size_t count = 0;
415 for (size_t i = 0; i < hm->capacity; i++) {
416 struct bucket *b = hm->buckets[i];
417 while (b) {
418 keys[count++] = strdup(b->key);
419 b = b->next;
420 }
421 }
422
423 *n_keys = count;
424 return keys;
425 }
426
427 void
428 hashmap_free_keys(char **keys, size_t n_keys)
429 {
430 if (!keys)
431 return;
432 for (size_t i = 0; i < n_keys; i++)
433 free(keys[i]);
434 free(keys);
435 }
436
437 struct hashmap *
438 rhashmap_new(arena *region, void (*val_free_func)(void *))
439 {
440 struct hashmap *h = hashmap_new(val_free_func);
441 assert(h);
442
443 arena_register(region, h, (void *)hashmap_free);
444 return h;
445 }