From d93eef112a5b5f004f2af168a7e34a19744a8456 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 2 Dec 2025 02:07:46 +0100 Subject: [PATCH] feat(hashmap): add hm_foreach and hm_clear --- hashmap/myhashmap.c | 257 +++++++++++++++++++++++++++++--------------- 1 file changed, 171 insertions(+), 86 deletions(-) diff --git a/hashmap/myhashmap.c b/hashmap/myhashmap.c index d407c9a..520bed1 100644 --- a/hashmap/myhashmap.c +++ b/hashmap/myhashmap.c @@ -13,7 +13,7 @@ static size_t get_mutex(hashmap_s *hashmap, size_t hash) { static size_t get_bucket_index(hashmap_s *hashmap, void *key) { unsigned int hash = hashmap->hash(key); - return hash % MYCLIB_HASHMAP_SIZE; + return (size_t)(hash % MYCLIB_HASHMAP_SIZE); } static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) { @@ -21,29 +21,38 @@ static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) { return; } - /* Free key if free function is provided */ - if (hashmap->free_key != NULL && bucket->key != NULL) { - hashmap->free_key(bucket->key); + /* Free key if free function is provided, otherwise free raw pointer if allocated */ + if (bucket->key != NULL) { + if (hashmap->free_key != NULL) { + hashmap->free_key(bucket->key); + } else { + free(bucket->key); + } + bucket->key = NULL; } - /* Free value if free function is provided */ - if (hashmap->free_value != NULL && bucket->value != NULL) { - hashmap->free_value(bucket->value); + /* Free value if free function is provided, otherwise free raw pointer if allocated */ + if (bucket->value != NULL) { + if (hashmap->free_value != NULL) { + hashmap->free_value(bucket->value); + } else { + free(bucket->value); + } + bucket->value = NULL; } } static bucket_s *find_bucket(hashmap_s *hashmap, void *key, bucket_s **prev) { size_t index = get_bucket_index(hashmap, key); bucket_s *bucket = &hashmap->map[index]; - *prev = NULL; - /* Return NULL if first bucket is empty */ + /* If primary bucket has no key, nothing stored here */ if (bucket->key == NULL) { return NULL; } - /* Search through the collision chain */ + /* Search through chain */ while (bucket != NULL) { if (hashmap->equal(bucket->key, key)) { return bucket; @@ -57,11 +66,17 @@ static bucket_s *find_bucket(hashmap_s *hashmap, void *key, bucket_s **prev) { hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn, free_value_f *free_value_fn, size_t key_size, size_t value_size) { + if (hash_fn == NULL || equal_fn == NULL || key_size == 0 || value_size == 0) { + return NULL; + } + hashmap_s *hashmap = malloc(sizeof(hashmap_s)); if (hashmap == NULL) { return NULL; } + memset(hashmap, 0, sizeof *hashmap); + hashmap->hash = hash_fn; hashmap->equal = equal_fn; hashmap->free_key = free_key_fn; @@ -75,7 +90,6 @@ hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn, hashmap->locks = malloc(sizeof(mtx_t) * hashmap->num_locks); if (hashmap->locks == NULL) { free(hashmap); - return NULL; } @@ -83,16 +97,18 @@ hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn, for (size_t i = 0; i < hashmap->num_locks; ++i) { ret = mtx_init(&(hashmap->locks[i]), mtx_recursive); if (ret != thrd_success) { - /* Mutex failed */ + /* Mutex failed: cleanup previous inits and return NULL */ for (size_t j = 0; j < i; ++j) { mtx_destroy(&(hashmap->locks[j])); } free(hashmap->locks); free(hashmap); + return NULL; } } + /* Initialize buckets to zero */ memset(hashmap->map, 0, sizeof(hashmap->map)); return hashmap; @@ -107,8 +123,7 @@ void hm_free(hashmap_s *hashmap) { for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { bucket_s *bucket = &hashmap->map[i]; - /* Free the first bucket if it contains data */ - if (bucket->key != NULL) { + if (bucket->key != NULL || bucket->value != NULL) { free_bucket_content(hashmap, bucket); } @@ -116,19 +131,31 @@ void hm_free(hashmap_s *hashmap) { bucket = bucket->next; while (bucket != NULL) { bucket_s *next = bucket->next; - free_bucket_content(hashmap, bucket); + if (bucket->key != NULL || bucket->value != NULL) { + if (hashmap->free_key != NULL && bucket->key != NULL) { + hashmap->free_key(bucket->key); + } else if (bucket->key != NULL) { + free(bucket->key); + } + + if (hashmap->free_value != NULL && bucket->value != NULL) { + hashmap->free_value(bucket->value); + } else if (bucket->value != NULL) { + free(bucket->value); + } + } free(bucket); bucket = next; } } - /* Free the mutex */ + /* Free the mutexes */ for (size_t i = 0; i < hashmap->num_locks; ++i) { mtx_destroy(&(hashmap->locks[i])); } free(hashmap->locks); - /* Free the hash map structure itself */ + /* Free the hash map structure */ free(hashmap); } @@ -147,35 +174,31 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { return false; } - size_t mutex_id = get_mutex(hashmap, hashmap->hash(key)); + size_t mutex_id = get_mutex(hashmap, (size_t)hashmap->hash(key)); mtx_t *mutex = &(hashmap->locks[mutex_id]); if (mtx_lock(mutex) != thrd_success) { return false; } - bucket_s *prev; + bucket_s *prev = NULL; bucket_s *existing = find_bucket(hashmap, key, &prev); if (existing != NULL) { - /* Key exists, update value */ if (hashmap->free_value != NULL && existing->value != NULL) { hashmap->free_value(existing->value); + } else if (existing->value != NULL) { + free(existing->value); } existing->value = malloc(hashmap->value_size); if (existing->value == NULL) { mtx_unlock(mutex); - return false; } memcpy(existing->value, value, hashmap->value_size); - /* Increase size */ - hashmap->size++; - mtx_unlock(mutex); - return true; } @@ -188,7 +211,6 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { bucket->key = malloc(hashmap->key_size); if (bucket->key == NULL) { mtx_unlock(mutex); - return false; } @@ -197,32 +219,32 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { free(bucket->key); bucket->key = NULL; mtx_unlock(mutex); - return false; } - hashmap->size++; memcpy(bucket->key, key, hashmap->key_size); memcpy(bucket->value, value, hashmap->value_size); bucket->next = NULL; - mtx_unlock(mutex); + hashmap->size++; + mtx_unlock(mutex); return true; } - /* Create new bucket and insert at head of collision chain */ + /* Collision: create new bucket and insert at head (after primary) */ bucket_s *new_bucket = malloc(sizeof(bucket_s)); if (new_bucket == NULL) { mtx_unlock(mutex); - return false; } + new_bucket->key = NULL; + new_bucket->value = NULL; + new_bucket->next = NULL; new_bucket->key = malloc(hashmap->key_size); if (new_bucket->key == NULL) { free(new_bucket); mtx_unlock(mutex); - return false; } @@ -231,44 +253,54 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { free(new_bucket->key); free(new_bucket); mtx_unlock(mutex); - return false; } - hashmap->size++; memcpy(new_bucket->key, key, hashmap->key_size); memcpy(new_bucket->value, value, hashmap->value_size); + + /* Insert new_bucket at head of chain */ new_bucket->next = bucket->next; bucket->next = new_bucket; + hashmap->size++; mtx_unlock(mutex); - return true; } static bucket_s *get_bucket_copy(bucket_s *from, size_t key_size, size_t value_size) { + if (from == NULL) { + return NULL; + } + bucket_s *copy = malloc(sizeof(bucket_s)); if (copy == NULL) { return NULL; } - memcpy(copy, from, sizeof(bucket_s)); + copy->key = NULL; + copy->value = NULL; + copy->next = NULL; - copy->key = malloc(key_size); - if (copy->key == NULL) { - free(copy); - - return NULL; + if (from->key != NULL) { + copy->key = malloc(key_size); + if (copy->key == NULL) { + free(copy); + return NULL; + } + memcpy(copy->key, from->key, key_size); } - memcpy(copy->key, from->key, key_size); - copy->value = malloc(value_size); - if (copy->value == NULL) { - free(copy->key); - free(copy); - - return NULL; + if (from->value != NULL) { + copy->value = malloc(value_size); + if (copy->value == NULL) { + free(copy->key); + free(copy); + return NULL; + } + memcpy(copy->value, from->value, value_size); } - memcpy(copy->value, from->value, value_size); + + copy->next = NULL; return copy; } @@ -278,25 +310,22 @@ bucket_s *hm_get(hashmap_s *hashmap, void *key) { return NULL; } - size_t mutex_id = get_mutex(hashmap, hashmap->hash(key)); + size_t mutex_id = get_mutex(hashmap, (size_t)hashmap->hash(key)); mtx_t *mutex = &(hashmap->locks[mutex_id]); if (mtx_lock(mutex) != thrd_success) { return NULL; } - bucket_s *prev; + bucket_s *prev = NULL; bucket_s *found = find_bucket(hashmap, key, &prev); if (found) { bucket_s *copy = get_bucket_copy(found, hashmap->key_size, hashmap->value_size); - mtx_unlock(mutex); - return copy; } mtx_unlock(mutex); - return NULL; } @@ -305,50 +334,47 @@ bool hm_remove(hashmap_s *hashmap, void *key) { return false; } - size_t mutex_id = get_mutex(hashmap, hashmap->hash(key)); + size_t mutex_id = get_mutex(hashmap, (size_t)hashmap->hash(key)); mtx_t *mutex = &(hashmap->locks[mutex_id]); if (mtx_lock(mutex) != thrd_success) { return false; } - bucket_s *prev; + bucket_s *prev = NULL; bucket_s *to_remove = find_bucket(hashmap, key, &prev); if (to_remove == NULL) { mtx_unlock(mutex); - return false; } - /* Free the content of the bucket */ - free_bucket_content(hashmap, to_remove); - - /* Handle removal based on position in chain */ if (prev == NULL) { - /* Removing first bucket in chain */ if (to_remove->next != NULL) { - /* Move next bucket's content to first bucket and free the next bucket */ bucket_s *next_bucket = to_remove->next; + + free_bucket_content(hashmap, to_remove); + to_remove->key = next_bucket->key; to_remove->value = next_bucket->value; to_remove->next = next_bucket->next; + free(next_bucket); } else { - /* No next bucket, mark first bucket as empty */ - to_remove->key = NULL; - to_remove->value = NULL; + free_bucket_content(hashmap, to_remove); to_remove->next = NULL; } } else { - /* Removing bucket from middle/end of chain */ + free_bucket_content(hashmap, to_remove); + prev->next = to_remove->next; free(to_remove); } - hashmap->size--; + if (hashmap->size > 0) { + hashmap->size--; + } mtx_unlock(mutex); - return true; } @@ -357,7 +383,7 @@ size_t hm_size(hashmap_s *hashmap) { return 0; } - /* Use the first mutex */ + /* Use the first mutex to protect read of size */ mtx_t *mutex = &hashmap->locks[0]; if (mtx_lock(mutex) != thrd_success) { return 0; @@ -375,23 +401,14 @@ bool hm_contains(hashmap_s *hashmap, void *key) { return false; } - bool res = true; + bool res = false; - size_t mutex_id = get_mutex(hashmap, hashmap->hash(key)); - mtx_t *mutex = &hashmap->locks[mutex_id]; - if (mtx_lock(mutex) != thrd_success) { - return false; + bucket_s *b = hm_get(hashmap, key); + if (b != NULL) { + res = true; + hm_free_bucket(b); } - bucket_s *bucket = hm_get(hashmap, key); - if (bucket == NULL) { - res = false; - } else { - hm_free_bucket(bucket); - } - - mtx_unlock(mutex); - return res; } @@ -400,7 +417,34 @@ void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) { return; } - // TODO + for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { + size_t mutex_id = get_mutex(hashmap, i); + mtx_t *mutex = &hashmap->locks[mutex_id]; + if (mtx_lock(mutex) != thrd_success) { + continue; + } + + bucket_s *bucket = &hashmap->map[i]; + if (bucket->key != NULL) { + bucket_s *copy = get_bucket_copy(bucket, hashmap->key_size, hashmap->value_size); + if (copy != NULL) { + callback(copy); + hm_free_bucket(copy); + } + } + + bucket = bucket->next; + while (bucket != NULL) { + bucket_s *copy = get_bucket_copy(bucket, hashmap->key_size, hashmap->value_size); + if (copy != NULL) { + callback(copy); + hm_free_bucket(copy); + } + bucket = bucket->next; + } + + mtx_unlock(mutex); + } } void hm_clear(hashmap_s *hashmap) { @@ -408,5 +452,46 @@ void hm_clear(hashmap_s *hashmap) { return; } - // TODO + for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { + size_t mutex_id = get_mutex(hashmap, i); + mtx_t *mutex = &hashmap->locks[mutex_id]; + if (mtx_lock(mutex) != thrd_success) { + continue; + } + + bucket_s *bucket = &hashmap->map[i]; + + if (bucket->key != NULL || bucket->value != NULL) { + free_bucket_content(hashmap, bucket); + } + + bucket = bucket->next; + while (bucket != NULL) { + bucket_s *next = bucket->next; + if (bucket->key != NULL) { + if (hashmap->free_key != NULL) { + hashmap->free_key(bucket->key); + } else { + free(bucket->key); + } + } + if (bucket->value != NULL) { + if (hashmap->free_value != NULL) { + hashmap->free_value(bucket->value); + } else { + free(bucket->value); + } + } + free(bucket); + bucket = next; + } + + hashmap->map[i].key = NULL; + hashmap->map[i].value = NULL; + hashmap->map[i].next = NULL; + + mtx_unlock(mutex); + } + + hashmap->size = 0; }