From 2480ec172702c38e9a2b9a3b2ab7d9cea49e95a5 Mon Sep 17 00:00:00 2001 From: Francesco Date: Sat, 2 Aug 2025 12:14:39 +0200 Subject: [PATCH] add strings --- .gitignore | 2 + hashmap/hashmap.c | 215 ----------------------------- hashmap/myhashmap.c | 188 +++++++++++++++++++++++++ hashmap/{hashmap.h => myhashmap.h} | 0 string/mystring.c | 111 +++++++++++++++ string/mystring.h | 72 ++++++++++ 6 files changed, 373 insertions(+), 215 deletions(-) delete mode 100644 hashmap/hashmap.c create mode 100644 hashmap/myhashmap.c rename hashmap/{hashmap.h => myhashmap.h} (100%) create mode 100644 string/mystring.c create mode 100644 string/mystring.h diff --git a/.gitignore b/.gitignore index 845cda6..cd03bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +test/ + # Prerequisites *.d diff --git a/hashmap/hashmap.c b/hashmap/hashmap.c deleted file mode 100644 index 3fae788..0000000 --- a/hashmap/hashmap.c +++ /dev/null @@ -1,215 +0,0 @@ -#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/myhashmap.c b/hashmap/myhashmap.c new file mode 100644 index 0000000..b93e9be --- /dev/null +++ b/hashmap/myhashmap.c @@ -0,0 +1,188 @@ +#include "myhashmap.h" + +#include +#include +#include + +static size_t mcl_get_bucket_index(mcl_hashmap *hashmap, void *key) { + unsigned int hash = hashmap->hash_fn(key); + return hash % MYCLIB_HASHMAP_SIZE; +} + +static void mcl_free_bucket_content(mcl_hashmap *hashmap, mcl_bucket *bucket) { + if (bucket == NULL) { + return; + } + + /* 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); + } +} + +static mcl_bucket *mcl_find_bucket(mcl_hashmap *hashmap, void *key, mcl_bucket **prev) { + size_t index = mcl_get_bucket_index(hashmap, key); + mcl_bucket *bucket = &hashmap->map[index]; + + *prev = NULL; + + /* Return NULL if first 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; + } + *prev = bucket; + bucket = bucket->next; + } + + return NULL; +} + +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) { + 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) { + mcl_free_bucket_content(hashmap, bucket); + } + + /* Free all chained buckets */ + bucket = bucket->next; + while (bucket != NULL) { + mcl_bucket *next = bucket->next; + mcl_free_bucket_content(hashmap, bucket); + free(bucket); + bucket = next; + } + } + + /* Free the hash map structure itself */ + free(hashmap); +} + +bool mcl_hm_set(mcl_hashmap *hashmap, void *key, void *value) { + if (hashmap == NULL || key == NULL) { + return false; + } + + /* Try to find existing bucket */ + mcl_bucket *prev; + mcl_bucket *existing = mcl_find_bucket(hashmap, key, &prev); + + if (existing != NULL) { + /* Key exists, update value */ + if (hashmap->free_value_fn != NULL && existing->value != NULL) { + hashmap->free_value_fn(existing->value); + } + existing->value = value; + return true; + } + + /* Key doesn't exist, need to insert new bucket */ + size_t index = mcl_get_bucket_index(hashmap, key); + mcl_bucket *bucket = &hashmap->map[index]; + + /* If first bucket is empty, use it */ + if (bucket->key == NULL) { + bucket->key = key; + bucket->value = value; + bucket->next = NULL; + return true; + } + + /* Create new bucket and insert at head of collision chain */ + mcl_bucket *new_bucket = malloc(sizeof(mcl_bucket)); + if (new_bucket == NULL) { + return false; + } + + 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) { + if (hashmap == NULL || key == NULL) { + return NULL; + } + + mcl_bucket *prev; + return mcl_find_bucket(hashmap, key, &prev); +} + +bool mcl_hm_remove(mcl_hashmap *hashmap, void *key) { + if (hashmap == NULL || key == NULL) { + return false; + } + + mcl_bucket *prev; + mcl_bucket *to_remove = mcl_find_bucket(hashmap, key, &prev); + + if (to_remove == NULL) { + return false; + } + + /* Free the content of the bucket */ + mcl_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 */ + mcl_bucket *next_bucket = to_remove->next; + 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; + to_remove->next = NULL; + } + } else { + /* Removing bucket from middle/end of chain */ + prev->next = to_remove->next; + free(to_remove); + } + + return true; +} diff --git a/hashmap/hashmap.h b/hashmap/myhashmap.h similarity index 100% rename from hashmap/hashmap.h rename to hashmap/myhashmap.h diff --git a/string/mystring.c b/string/mystring.c new file mode 100644 index 0000000..932e20e --- /dev/null +++ b/string/mystring.c @@ -0,0 +1,111 @@ +#include "mystring.h" + +#include +#include +#include + +mcl_string *mcl_string_new(const char *text) { + if (!text) { + return NULL; + } + + /* Allocate string struct */ + mcl_string *str = malloc(sizeof(mcl_string)); + if (!str) { + return NULL; + } + + /* Calculate size and capacity */ + str->size = strlen(text); + + size_t capacity = (unsigned long)pow(2, (unsigned)log2(str->size) + 1); + str->capacity = capacity; + + /* Allocate data buffer */ + str->data = malloc(sizeof(char) * str->capacity); + if (!str->data) { + free(str); + + return NULL; + } + + /* Copy the text and ensure null termination */ + memset(str->data, 0, str->capacity); + memcpy(str->data, text, str->size); + str->data[str->size] = '\0'; + + return str; +} + +int mcl_string_append(mcl_string *string, const char *text) { + if (!string || !text) { + return -1; + } + + /* Handle empty case */ + size_t text_len = strlen(text); + if (text_len == 0) { + return 0; + } + + size_t new_size = text_len + string->size; + + /* Check if we need to resize */ + if (new_size + 1 > string->capacity) { + size_t new_capacity = (unsigned long)pow(2, (unsigned)log2(new_size) + 1); + /* Reallocate the buffer */ + void *new_data = realloc(string->data, sizeof(char) * new_capacity); + if (!new_data) { + return -1; + } + + string->data = new_data; + string->capacity = new_capacity; + + /* Init to 0 the new capacity */ + memset(string->data + string->size, 0, string->capacity - string->size); + } + + /* Append text */ + memcpy(string->data + string->size, text, text_len); + string->size = new_size; + string->data[string->size] = '\0'; + + return 0; +} + +void mcl_string_free(mcl_string *string) { + if (!string) { + return; + } + + if (string->data) { + free(string->data); + } + + free(string); +} + +size_t mcl_string_length(const mcl_string *string) { + if (!string) { + return 0; + } + + return string->size; +} + +size_t mcl_string_capacity(const mcl_string *string) { + if (!string) { + return 0; + } + + return string->capacity; +} + +const char *mcl_string_cstr(const mcl_string *string) { + if (!string || !string->data) { + return ""; + } + + return string->data; +} diff --git a/string/mystring.h b/string/mystring.h new file mode 100644 index 0000000..63a98df --- /dev/null +++ b/string/mystring.h @@ -0,0 +1,72 @@ +#ifndef MYCLIB_STRING_H +#define MYCLIB_STRING_H + +#include + +/** + * @brief String structure + */ +typedef struct mcl_string_t { + size_t size; /**< Length of the string (excluding null terminator) */ + size_t capacity; /**< Total allocated capacity */ + char *data; /**< Pointer to the string data */ +} mcl_string; + +/** + * @brief Create a new string + * + * @param text The text to initialize from + * @return Pointer to the new string, or NULL on failure + * + * @note The caller is responsible for freeing the returned string with mcl_string_free() + */ +mcl_string *mcl_string_new(const char *text); + +/** + * @brief Append text to an existing string + * + * @param string The string to append to + * @param text The string to append (can be empty) + * @return 0 on success, -1 on failure + * + * @note If it fails, the original string remains unchanged + */ +int mcl_string_append(mcl_string *string, const char *text); + +/** + * @brief Free a string + * + * @param string The string to free + * + * @note This function is safe to call with NULL pointers + */ +void mcl_string_free(mcl_string *string); + +/** + * @brief Get the current length of the string + * + * @param string The string to query + * @return The length of the string, or 0 if string is NULL + */ +size_t mcl_string_length(const mcl_string *string); + +/** + * @brief Get the current capacity of the string + * + * @param string The string to query + * @return The capacity of the string buffer, or 0 if string is NULL + */ +size_t mcl_string_capacity(const mcl_string *string); + +/** + * @brief Get a read-only string representation + * + * @param string The string to access + * @return Pointer to null-terminated string data, or empty string "" if string is NULL + * + * @warning The returned pointer should not be modified directly and may become + * invalid after any modification operation on the string + */ +const char *mcl_string_cstr(const mcl_string *string); + +#endif /* MYCLIB_STRING_H */