diff --git a/README.md b/README.md index 4c550c1..e2eaefd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ # libhash -Collision-free hash tables and hash sets for C. - -Implemented as open-addressed and double-hashed. - -For best performance, initialize with a prime number. - -## TODOs - -- [ ] add docs -- [ ] complete examples +* Collision-free hash tables and hash sets for C. +* Implemented as open-addressed and double-hashed. +* Extremely simple and easy-to-use API. +* For documentation, see the header file [here](include/libhash.h). +* For best performance, initialize with a prime number. +* For examples, see [examples](examples/main.c) diff --git a/clib.json b/clib.json index 5f50cae..bffbb5d 100644 --- a/clib.json +++ b/clib.json @@ -1,6 +1,6 @@ { "name": "libhash", - "version": "0.0.7", + "version": "1.0.0", "author": "Matthew Zito", "repo": "exbotanical/libhash", "license": "MIT", diff --git a/examples/main.c b/examples/main.c index 03e28fa..0494acc 100644 --- a/examples/main.c +++ b/examples/main.c @@ -3,11 +3,13 @@ #include "libhash.h" int main(int argc, char const* argv[]) { - hash_table* ht = ht_init(0); + hash_table* ht = ht_init(3, NULL); - ht_insert(ht, "key", "value2"); + ht_insert(ht, "key1", "value1"); + ht_insert(ht, "key2", "value2"); + ht_insert(ht, "key3", "value3"); - printf("value: %s", ht_get(ht, "key")); + printf("value for key1: %s", ht_get(ht, "key")); return 0; } diff --git a/include/libhash.h b/include/libhash.h index 010820b..41c6566 100644 --- a/include/libhash.h +++ b/include/libhash.h @@ -4,6 +4,14 @@ #define HT_DEFAULT_CAPACITY 50 #define HS_DEFAULT_CAPACITY 50 +/** + * A free function that will be invoked a hashmap value any time it is removed. + * + * @param value + * @return typedef + */ +typedef void free_fn(void *value); + /** * A hash table entry i.e. key / value pair */ @@ -36,15 +44,23 @@ typedef struct { * The hash table's entries */ ht_entry **entries; + + /** + * Either a free_fn* or NULL; if set, this function pointer will be invoked + * with hashmap values that are being removed so the caller may free them + * however they want. + */ + free_fn *free_value; } hash_table; /** * Initialize a new hash table with a size of `max_size` * * @param max_size The hash table capacity + * @param free_value See free_fn * @return hash_table* */ -hash_table *ht_init(int base_capacity); +hash_table *ht_init(int base_capacity, free_fn *free_value); /** * Insert a key, value pair into the given hash table. @@ -54,18 +70,6 @@ hash_table *ht_init(int base_capacity); */ void ht_insert(hash_table *ht, const char *key, void *value); -/** - * Insert a key, value pair into the given hash table. - * - * This version of ht_insert will also free the entry value. - * Thus, the entry value must be allocated on the heap. - * - * @param ht - * @param key - * @param value - */ -void ht_insert_ptr(hash_table *ht, const char *key, void *value); - /** * Search for the entry corresponding to the given key * @@ -90,16 +94,6 @@ void *ht_get(hash_table *ht, const char *key); */ void ht_delete_table(hash_table *ht); -/** - * Delete a hash table and deallocate its memory - * - * This version of ht_delete_table will also free the entry value. - * Thus, the entry value must be allocated on the heap. - * - * @param ht Hash table to delete - */ -void ht_delete_table_ptr(hash_table *ht); - /** * Delete a entry for the given key `key`. Because entries * may be part of a collision chain, and removing them completely @@ -114,23 +108,6 @@ void ht_delete_table_ptr(hash_table *ht); */ int ht_delete(hash_table *ht, const char *key); -/** - * Delete a entry for the given key `key`. Because entries - * may be part of a collision chain, and removing them completely - * could cause infinite lookup attempts, we replace the deleted entry - * with a NULL sentinel entry. - * - * This version of ht_delete will also free the entry value. - * Thus, the entry value must be allocated on the heap. - * - * @param ht - * @param key - * - * @return 1 if a entry was deleted, 0 if no entry corresponding - * to the given key could be found - */ -int ht_delete_ptr(hash_table *ht, const char *key); - typedef struct { /** * Max number of keys which may be stored in the hash set. Adjustable. diff --git a/src/hash_table.c b/src/hash_table.c index 2142192..d778125 100644 --- a/src/hash_table.c +++ b/src/hash_table.c @@ -9,10 +9,9 @@ static ht_entry HT_SENTINEL_ENTRY = {NULL, NULL}; -static void __ht_insert(hash_table *ht, const char *key, void *value, - bool free_value); -static int __ht_delete(hash_table *ht, const char *key, bool free_value); -static void __ht_delete_table(hash_table *ht, bool free_value); +static void __ht_insert(hash_table *ht, const char *key, void *value); +static int __ht_delete(hash_table *ht, const char *key); +static void __ht_delete_table(hash_table *ht); /** * Resize the hash table. This implementation has a set capacity; @@ -26,19 +25,18 @@ static void __ht_delete_table(hash_table *ht, bool free_value); * @param base_capacity * @return int */ -static void ht_resize(hash_table *ht, const int base_capacity, - bool free_value) { +static void ht_resize(hash_table *ht, const int base_capacity) { if (base_capacity < 0) { return; } - hash_table *new_ht = ht_init(base_capacity); + hash_table *new_ht = ht_init(base_capacity, ht->free_value); for (unsigned int i = 0; i < ht->capacity; i++) { ht_entry *r = ht->entries[i]; if (r != NULL && r != &HT_SENTINEL_ENTRY) { - __ht_insert(new_ht, r->key, r->value, free_value); + __ht_insert(new_ht, r->key, r->value); } } @@ -63,10 +61,10 @@ static void ht_resize(hash_table *ht, const int base_capacity, * * @param ht */ -static void ht_resize_up(hash_table *ht, bool free_value) { +static void ht_resize_up(hash_table *ht) { const int new_capacity = ht->base_capacity * 2; - ht_resize(ht, new_capacity, free_value); + ht_resize(ht, new_capacity); } /** @@ -75,10 +73,10 @@ static void ht_resize_up(hash_table *ht, bool free_value) { * * @param ht */ -static void ht_resize_down(hash_table *ht, bool free_value) { +static void ht_resize_down(hash_table *ht) { const int new_capacity = ht->base_capacity / 2; - ht_resize(ht, new_capacity, free_value); + ht_resize(ht, new_capacity); } /** @@ -101,24 +99,23 @@ static ht_entry *ht_entry_init(const char *k, void *v) { * * @param r entry to delete */ -static void ht_delete_entry(ht_entry *r, bool free_value) { +static void ht_delete_entry(ht_entry *r, free_fn *maybe_free_value) { free(r->key); - if (free_value) { - free(r->value); + if (maybe_free_value) { + maybe_free_value(r->value); r->value = NULL; } free(r); } -static void __ht_insert(hash_table *ht, const char *key, void *value, - bool free_value) { +static void __ht_insert(hash_table *ht, const char *key, void *value) { if (ht == NULL) { return; } const int load = ht->count * 100 / ht->capacity; if (load > 70) { - ht_resize_up(ht, free_value); + ht_resize_up(ht); } ht_entry *new_entry = ht_entry_init(key, value); @@ -132,7 +129,7 @@ static void __ht_insert(hash_table *ht, const char *key, void *value, while (current_entry != NULL && current_entry != &HT_SENTINEL_ENTRY) { // update existing key/value if (strcmp(current_entry->key, key) == 0) { - ht_delete_entry(current_entry, free_value); + ht_delete_entry(current_entry, ht->free_value); ht->entries[idx] = new_entry; return; @@ -148,11 +145,11 @@ static void __ht_insert(hash_table *ht, const char *key, void *value, ht->count++; } -static int __ht_delete(hash_table *ht, const char *key, bool free_value) { +static int __ht_delete(hash_table *ht, const char *key) { const int load = ht->count * 100 / ht->capacity; if (load < 10) { - ht_resize_down(ht, free_value); + ht_resize_down(ht); } int i = 0; @@ -162,7 +159,7 @@ static int __ht_delete(hash_table *ht, const char *key, bool free_value) { while (current_entry != NULL && current_entry != &HT_SENTINEL_ENTRY) { if (strcmp(current_entry->key, key) == 0) { - ht_delete_entry(current_entry, free_value); + ht_delete_entry(current_entry, ht->free_value); ht->entries[idx] = &HT_SENTINEL_ENTRY; ht->count--; @@ -177,12 +174,12 @@ static int __ht_delete(hash_table *ht, const char *key, bool free_value) { return 0; } -static void __ht_delete_table(hash_table *ht, bool free_value) { +static void __ht_delete_table(hash_table *ht) { for (unsigned int i = 0; i < ht->capacity; i++) { ht_entry *r = ht->entries[i]; if (r != NULL && r != &HT_SENTINEL_ENTRY) { - ht_delete_entry(r, free_value); + ht_delete_entry(r, ht->free_value); } } @@ -190,7 +187,7 @@ static void __ht_delete_table(hash_table *ht, bool free_value) { free(ht); } -hash_table *ht_init(int base_capacity) { +hash_table *ht_init(int base_capacity, free_fn *free_value) { if (!base_capacity) { base_capacity = HT_DEFAULT_CAPACITY; } @@ -201,16 +198,13 @@ hash_table *ht_init(int base_capacity) { ht->capacity = next_prime(ht->base_capacity); ht->count = 0; ht->entries = calloc((size_t)ht->capacity, sizeof(ht_entry *)); + ht->free_value = free_value; return ht; } void ht_insert(hash_table *ht, const char *key, void *value) { - __ht_insert(ht, key, value, false); -} - -void ht_insert_ptr(hash_table *ht, const char *key, void *value) { - __ht_insert(ht, key, value, true); + __ht_insert(ht, key, value); } ht_entry *ht_search(hash_table *ht, const char *key) { @@ -237,14 +231,6 @@ void *ht_get(hash_table *ht, const char *key) { return r ? r->value : NULL; } -void ht_delete_table(hash_table *ht) { __ht_delete_table(ht, false); } - -void ht_delete_table_ptr(hash_table *ht) { __ht_delete_table(ht, true); } +void ht_delete_table(hash_table *ht) { __ht_delete_table(ht); } -int ht_delete(hash_table *ht, const char *key) { - return __ht_delete(ht, key, false); -} - -int ht_delete_ptr(hash_table *ht, const char *key) { - return __ht_delete(ht, key, true); -} +int ht_delete(hash_table *ht, const char *key) { return __ht_delete(ht, key); } diff --git a/t/hash_table_test.c b/t/hash_table_test.c index 35837be..f27bc0a 100644 --- a/t/hash_table_test.c +++ b/t/hash_table_test.c @@ -12,7 +12,7 @@ void test_initialization(void) { char *k = "key"; char *v = "value"; - hash_table *ht = ht_init(capacity); + hash_table *ht = ht_init(capacity, NULL); ht_entry *r = ht_entry_init(k, v); ok(ht != NULL, "hash table is not NULL"); @@ -31,7 +31,7 @@ void test_initialization(void) { } void test_insert(void) { - hash_table *ht = ht_init(10); + hash_table *ht = ht_init(10, NULL); ht_insert(ht, "k1", "v1"); @@ -58,7 +58,7 @@ void test_insert(void) { } void test_search(void) { - hash_table *ht = ht_init(10); + hash_table *ht = ht_init(10, NULL); ht_insert(ht, "k1", "v1"); ht_insert(ht, "k2", "v2"); @@ -76,7 +76,7 @@ void test_search(void) { } void test_delete(void) { - hash_table *ht = ht_init(10); + hash_table *ht = ht_init(10, NULL); ht_insert(ht, "k1", "v1"); ht_insert(ht, "k2", "v2"); @@ -97,7 +97,7 @@ void test_delete(void) { void test_capacity(void) { int initial_cap = 23; - hash_table *ht = ht_init(initial_cap); + hash_table *ht = ht_init(initial_cap, NULL); for (int i = 0; i < initial_cap; i++) { double digits = i == 0 ? 1 : floor(log10(abs(i))) + 1; @@ -114,27 +114,28 @@ void test_capacity(void) { ok(ht->count == initial_cap, "maintains the count"); } -void test_delete_ptr(void) { - hash_table *ht = ht_init(10); +void *free(void *value) { free((char *)value); } +void test_delete_with_free(void) { + hash_table *ht = ht_init(10, free); char *v1 = strdup("v1"); char *v2 = strdup("v2"); char *v3 = strdup("v3"); - ht_insert_ptr(ht, "k1", v1); - ht_insert_ptr(ht, "k2", v2); - ht_insert_ptr(ht, "k3", v3); + ht_insert(ht, "k1", v1); + ht_insert(ht, "k2", v2); + ht_insert(ht, "k3", v3); - ok(ht_delete_ptr(ht, "k1") == 1, - "returns 1 when entry deletion was successful"); - ok(ht_delete_ptr(ht, "k2") == 1, - "returns 1 when entry deletion was successful"); - ok(ht_delete_ptr(ht, "k3") == 1, - "returns 1 when entry deletion was successful"); + ok(ht_delete(ht, "k1") == 1, "returns 1 when entry deletion was successful"); + ok(v1 == NULL, "passes the value to the provided free function"); + ok(ht_delete(ht, "k2") == 1, "returns 1 when entry deletion was successful"); + ok(v2 == NULL, "passes the value to the provided free function"); + ok(ht_delete(ht, "k3") == 1, "returns 1 when entry deletion was successful"); + ok(v3 == NULL, "passes the value to the provided free function"); - ok(ht_delete_ptr(ht, "k4") == 0, + ok(ht_delete(ht, "k4") == 0, "returns 0 when entry deletion was unsuccessful"); - ok(ht_delete_ptr(ht, "k1") == 0, "cannot delete the same entry twice"); + ok(ht_delete(ht, "k1") == 0, "cannot delete the same entry twice"); is(ht_get(ht, "k1"), NULL, "returns NULL because the entry has been deleted"); is(ht_get(ht, "k2"), NULL, "returns NULL because the entry has been deleted"); @@ -154,7 +155,7 @@ int main() { test_search(); test_delete(); test_capacity(); - test_delete_ptr(); + test_delete_with_free(); done_testing(); }