Skip to content

Commit

Permalink
feat(hash_table): support custom free value function
Browse files Browse the repository at this point in the history
  • Loading branch information
exbotanical committed Jul 28, 2024
1 parent 85b77dd commit 97d239f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 113 deletions.
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion clib.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "libhash",
"version": "0.0.7",
"version": "1.0.0",
"author": "Matthew Zito",
"repo": "exbotanical/libhash",
"license": "MIT",
Expand Down
8 changes: 5 additions & 3 deletions examples/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
57 changes: 17 additions & 40 deletions include/libhash.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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.
Expand All @@ -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
*
Expand All @@ -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
Expand All @@ -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.
Expand Down
66 changes: 26 additions & 40 deletions src/hash_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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--;
Expand All @@ -177,20 +174,20 @@ 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);
}
}

free(ht->entries);
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;
}
Expand All @@ -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) {
Expand All @@ -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); }
Loading

0 comments on commit 97d239f

Please sign in to comment.