agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs

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 }