diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a6079ec --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +BasedOnStyle: Google +ColumnLimit: 160 +UseTab: Always +IndentWidth: 4 +TabWidth: 4 diff --git a/hashmap/hashmap.c b/hashmap/hashmap.c new file mode 100644 index 0000000..3fae788 --- /dev/null +++ b/hashmap/hashmap.c @@ -0,0 +1,215 @@ +#include "hashmap.h" + +#include +#include +#include + +mcl_hashmap *mcl_hm_init(mcl_hash_fn *hash_fn, mcl_equal_fn *equal_fn, mcl_free_key_fn *free_key_fn, mcl_free_value_fn *free_value_fn) { + /* Allocate memory for hash map struct */ + mcl_hashmap *hashmap = malloc(sizeof(mcl_hashmap)); + + if (hashmap == NULL) { + return NULL; + } + + /* Initialize hash map with given parameters */ + hashmap->hash_fn = hash_fn; + hashmap->equal_fn = equal_fn; + hashmap->free_key_fn = free_key_fn; + hashmap->free_value_fn = free_value_fn; + + /* Clear all buckets in the map */ + memset(hashmap->map, 0, sizeof(hashmap->map)); + + return hashmap; +} + +void mcl_hm_free(mcl_hashmap *hashmap) { + if (hashmap == NULL) { + return; + } + + /* Iterate through all buckets in the hash map */ + for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { + mcl_bucket *bucket = &hashmap->map[i]; + + /* Free the first bucket if it contains data */ + if (bucket->key != NULL) { + if (hashmap->free_key_fn != NULL) { + hashmap->free_key_fn(bucket->key); + } + if (hashmap->free_value_fn != NULL) { + hashmap->free_value_fn(bucket->value); + } + } + + /* Free all chained buckets */ + bucket = bucket->next; + while (bucket != NULL) { + if (hashmap->free_key_fn != NULL) { + hashmap->free_key_fn(bucket->key); + } + if (hashmap->free_value_fn != NULL) { + hashmap->free_value_fn(bucket->value); + } + mcl_bucket *next = bucket->next; + free(bucket); + bucket = next; + } + } + + /* Free the hash map structure itself */ + free(hashmap); +} + +bool mcl_hm_set(mcl_hashmap *hashmap, void *key, void *value) { + /* Validate input parameters */ + if (hashmap == NULL || key == NULL) { + return false; + } + + /* Calculate hash index for the key */ + int index = hashmap->hash_fn(key) % MYCLIB_HASHMAP_SIZE; + mcl_bucket *bucket = &hashmap->map[index]; + + /* If bucket is empty, insert new key-value pair */ + if (bucket->key == NULL) { + bucket->key = key; + bucket->value = value; + bucket->next = NULL; + return true; + } + + /* Check if first bucket has the same key */ + if (hashmap->equal_fn(bucket->key, key)) { + /* Update existing value, free old value if needed */ + if (hashmap->free_value_fn != NULL && bucket->value != NULL) { + hashmap->free_value_fn(bucket->value); + } + bucket->value = value; + return true; + } + + /* Search through the collision chain */ + mcl_bucket *current = bucket->next; + while (current != NULL) { + if (hashmap->equal_fn(current->key, key)) { + /* Update existing value, free old value if needed */ + if (hashmap->free_value_fn != NULL && current->value != NULL) { + hashmap->free_value_fn(current->value); + } + current->value = value; + return true; + } + current = current->next; + } + + /* Key not found, create new bucket and add to chain */ + mcl_bucket *new_bucket = malloc(sizeof(mcl_bucket)); + if (new_bucket == NULL) { + return false; /* Memory allocation failed */ + } + + /* Initialize new bucket and insert at head of chain */ + new_bucket->key = key; + new_bucket->value = value; + new_bucket->next = bucket->next; + bucket->next = new_bucket; + + return true; +} + +mcl_bucket *mcl_hm_get(mcl_hashmap *hashmap, void *key) { + /* Validate input parameters */ + if (hashmap == NULL || key == NULL) { + return NULL; + } + + /* Calculate hash index for the key */ + int index = hashmap->hash_fn(key) % MYCLIB_HASHMAP_SIZE; + mcl_bucket *bucket = &hashmap->map[index]; + + /* Return NULL if bucket is empty */ + if (bucket->key == NULL) { + return NULL; + } + + /* Search through the collision chain */ + while (bucket != NULL) { + if (hashmap->equal_fn(bucket->key, key)) { + return bucket; /* Key found */ + } + bucket = bucket->next; + } + + /* Key not found */ + return NULL; +} + +static void mcl_free_bucket_content(mcl_hashmap *hashmap, mcl_bucket *bucket) { + /* Free key if free function is provided */ + if (hashmap->free_key_fn != NULL && bucket->key != NULL) { + hashmap->free_key_fn(bucket->key); + } + + /* Free value if free function is provided */ + if (hashmap->free_value_fn != NULL && bucket->value != NULL) { + hashmap->free_value_fn(bucket->value); + } +} + +bool mcl_hm_remove(mcl_hashmap *hashmap, void *key) { + /* Validate input parameters */ + if (hashmap == NULL || key == NULL) { + return false; + } + + /* Calculate hash index for the key */ + int index = hashmap->hash_fn(key) % MYCLIB_HASHMAP_SIZE; + mcl_bucket *bucket = &hashmap->map[index]; + + /* Return false if bucket is empty */ + if (bucket->key == NULL) { + return false; + } + + /* Check if first bucket contains the key to remove */ + if (hashmap->equal_fn(bucket->key, key)) { + /* Free the content of the bucket */ + mcl_free_bucket_content(hashmap, bucket); + + if (bucket->next != NULL) { + /* Move next bucket's content to first bucket and free the next bucket */ + mcl_bucket *to_free = bucket->next; + bucket->key = to_free->key; + bucket->value = to_free->value; + bucket->next = to_free->next; + free(to_free); + } else { + /* No next bucket, mark first bucket as empty */ + bucket->key = NULL; + bucket->value = NULL; + bucket->next = NULL; + } + return true; + } + + /* Search through the collision chain */ + mcl_bucket *prev = bucket; + mcl_bucket *current = bucket->next; + + while (current != NULL) { + if (hashmap->equal_fn(current->key, key)) { + /* Key found, free content and unlink bucket */ + mcl_free_bucket_content(hashmap, current); + prev->next = current->next; + free(current); + return true; + } + prev = current; + current = current->next; + } + + /* Key not found */ + return false; +} diff --git a/hashmap/hashmap.h b/hashmap/hashmap.h new file mode 100644 index 0000000..9b4e96d --- /dev/null +++ b/hashmap/hashmap.h @@ -0,0 +1,127 @@ +#ifndef MYCLIB_HASHMAP_H +#define MYCLIB_HASHMAP_H + +#include +#include + +#define MYCLIB_HASHMAP_SIZE 1024 /**< Number of buckets in the hash map */ + +/** + * @brief A single bucket in the hash map + * + * Each bucket can hold one key-value pair and points to the next bucket + * in case of hash collisions (separate chaining). + */ +typedef struct mcl_bucket_t { + void *key; /**< Pointer to the key */ + void *value; /**< Pointer to the value */ + struct mcl_bucket_t *next; /**< Pointer to the next bucket in case of collision */ +} mcl_bucket; + +/** + * @brief Function pointer type for a hash function + * + * @param[in] key Pointer to the key to hash + * @return The computed hash as an unsigned integer + */ +typedef unsigned int mcl_hash_fn(const void *key); + +/** + * @brief Function pointer type for a key comparison function + * + * @param[in] key_a Pointer to the first key + * @param[in] key_b Pointer to the second key + * @return true if the keys are considered equal, false otherwise + */ +typedef bool mcl_equal_fn(const void *key_a, const void *key_b); + +/** + * @brief Function pointer type for freeing a key + * + * @param[in] key Pointer to the key to free + */ +typedef void mcl_free_key_fn(void *key); + +/** + * @brief Function pointer type for freeing a value + * + * @param[in] value Pointer to the value to free + */ +typedef void mcl_free_value_fn(void *value); + +/** + * @brief Main structure representing the hash map + * + * Contains function pointers for hash computation, key comparison, + * and memory management, along with the bucket array. + */ +typedef struct mcl_hashmap_t { + mcl_hash_fn *hash_fn; /**< Hash function */ + mcl_equal_fn *equal_fn; /**< Equality comparison function */ + mcl_free_key_fn *free_key_fn; /**< Key deallocation function (optional) */ + mcl_free_value_fn *free_value_fn; /**< Value deallocation function (optional) */ + mcl_bucket map[MYCLIB_HASHMAP_SIZE]; /**< Array of bucket chains */ +} mcl_hashmap; + +/** + * @brief Initialize a new hash map with user-defined behavior functions + * + * Creates a new hash map and initializes it with the provided function pointers. + * The free functions can be NULL if no automatic memory management is needed. + * + * @param[in] hash_fn Function used to hash keys (required) + * @param[in] equal_fn Function used to compare keys (required) + * @param[in] free_key_fn Function used to free keys (optional, can be NULL) + * @param[in] free_value_fn Function used to free values (optional, can be NULL) + * @return A pointer to the newly initialized hash map, or NULL on failure + */ +mcl_hashmap *mcl_hm_init(mcl_hash_fn *hash_fn, mcl_equal_fn *equal_fn, mcl_free_key_fn *free_key_fn, mcl_free_value_fn *free_value_fn); + +/** + * @brief Free all resources used by the hash map + * + * Iterates through all buckets, frees keys and values using the provided + * free functions (if not NULL), and deallocates the hash map structure. + * + * @param[in] hashmap Pointer to the hash map to free + */ +void mcl_hm_free(mcl_hashmap *hashmap); + +/** + * @brief Insert or update a key-value pair in the hash map + * + * If the key already exists, the old value is freed (if free_value_fn is provided) + * and replaced with the new value. If the key doesn't exist, a new entry is created. + * + * @param[in] hashmap Pointer to the hash map + * @param[in] key Pointer to the key to insert (must not be NULL) + * @param[in] value Pointer to the value to insert (can be NULL) + * @return true if the operation succeeded, false on failure (NULL hashmap/key or memory allocation failure) + */ +bool mcl_hm_set(mcl_hashmap *hashmap, void *key, void *value); + +/** + * @brief Retrieve a bucket by key + * + * Searches for the given key in the hash map and returns the bucket containing it. + * The caller can then access both the key and value from the returned bucket. + * + * @param[in] hashmap Pointer to the hash map + * @param[in] key Pointer to the key to search for + * @return Pointer to the found bucket, or NULL if not found or on invalid input + */ +mcl_bucket *mcl_hm_get(mcl_hashmap *hashmap, void *key); + +/** + * @brief Remove a key-value pair from the hash map + * + * Searches for the given key and removes it from the hash map. Both the key + * and value are freed using the provided free functions (if not NULL). + * + * @param[in] hashmap Pointer to the hash map + * @param[in] key Pointer to the key to remove + * @return true if the key was found and removed, false if not found or on invalid input + */ +bool mcl_hm_remove(mcl_hashmap *hashmap, void *key); + +#endif /* MYCLIB_HASHMAP_H */