I implemented a hashtable that handles collisions by chaining entries using a double linked list. The idea is that it can store/lookup any kind of generic data structure, while maintaining an API that is convenient to use.
For now the implementation only supports adding, obtaining and removing entries, - resizing of the table is planned in the future.
Can I make my implementation more performant and readable, so that I can easiely reuse it in future projects?
I feel like the ht_add
and ht_delete
functions especially have room for improvement. Looking forward for any feedback.
hashtable.h
#ifndef HASHTABLE_H
#define HASHTABLE_H
typedef struct entry_t
{
unsigned int key;
void* value;
struct entry_t* next;
struct entry_t* prev;
} entry_t;
typedef struct hashtable_t
{
unsigned int buckets_count;
struct entry_t **buckets;
} hashtable_t;
// ToDo: Add resizing functionality
unsigned int compute_hash(struct hashtable_t *ht, unsigned int key);
hashtable_t *ht_new(unsigned int size);
int ht_add(struct hashtable_t* ht, unsigned int key, void* value);
entry_t* ht_get(struct hashtable_t* ht, unsigned int key);
void ht_print(struct hashtable_t* ht);
void ht_delete(struct hashtable_t* ht, unsigned int key);
void ht_free(struct hashtable_t* ht);
#endif
hashtable.c
#include "hashtable.h"
#include <stdlib.h>
#include <stdio.h>
// Sources used as reference/learning material:
// http://pokristensson.com/code/strmap/strmap.c
// https://github.com/Encrylize/hashmap/blob/master/hashmap.c
// https://github.com/goldsborough/hashtable/blob/master/hashtable.c
hashtable_t *ht_new(unsigned int bucket_count)
{
struct hashtable_t *ht;
ht = malloc(sizeof(struct hashtable_t));
if (ht == NULL)
return NULL;
ht->buckets = malloc(sizeof(entry_t) * bucket_count);
if (ht->buckets == NULL)
return NULL;
ht->buckets_count = bucket_count;
return ht;
}
void ht_delete(struct hashtable_t *ht, unsigned int key)
{
unsigned int index = compute_hash(ht, key);
entry_t *e_curr = ht_get(ht, key);
/* Chain has no entries */
if (e_curr == NULL)
return;
entry_t *e_prev = e_curr->prev;
entry_t *e_next = e_curr->next;
/* Entry is first element in the chain */
if(e_prev == NULL)
{
if(e_next != NULL)
{
e_next->prev = NULL;
}
ht->buckets[index] = e_next;
free(e_curr);
}
/* Entry is not the first element in the chain */
else
{
while(e_curr->key != key)
{
e_curr = e_next;
e_prev = e_curr;
}
e_prev->next = e_next;
if(e_next != NULL)
e_next->prev = e_prev;
free(e_curr);
}
}
int ht_add(struct hashtable_t *ht, unsigned int key, void* value)
{
unsigned int index = compute_hash(ht, key);
struct entry_t *new_entry = malloc(sizeof(struct entry_t));
if (new_entry == NULL)
return -1;
if (ht->buckets[index] != NULL)
{
/* Go to the end of the linked list and append new entry */
entry_t *current_entry = ht->buckets[index];
while (current_entry->next != NULL)
{
current_entry = current_entry->next;
}
new_entry->key = key;
new_entry->value = value;
new_entry->next = NULL;
new_entry->prev = current_entry;
current_entry->next = new_entry;
} else
{
new_entry->key = key;
new_entry->value = value;
new_entry->next = NULL;
new_entry->prev = NULL;
ht->buckets[index] = new_entry;
}
return 0;
}
// Taken from https://burtleburtle.net/bob/hash/integer.html
// Should give a fairly decent distribution of entries
unsigned int compute_hash(struct hashtable_t *ht, unsigned int key)
{
key = (key ^ 61) ^ (key >> 16);
key = key + (key << 3);
key = key ^ (key >> 4);
key = key * 0x27d4eb2d;
key = key ^ (key >> 15);
return key % ht->buckets_count;
}
void ht_print(struct hashtable_t *ht)
{
for (int i = 0; i < ht->buckets_count; ++i)
{
struct entry_t *e_curr = ht->buckets[i];
printf("[%d]", i);
do
{
if (e_curr == NULL)
{
printf(" NULL\n");
break;
}
printf(" %d <->", e_curr->key);
if (e_curr->next == NULL)
{
printf(" NULL\n");
break;
}
e_curr = e_curr->next;
} while (1);
}
}
entry_t *ht_get(struct hashtable_t *ht, unsigned int key)
{
unsigned int index = compute_hash(ht, key);
entry_t *e_curr = ht->buckets[index];
while (e_curr->key != key)
{
e_curr = e_curr->next;
/* Specified key does not exist in ht */
if (e_curr == NULL)
return NULL;
}
return e_curr;
}
void ht_free(struct hashtable_t *ht)
{
free(ht->buckets);
free(ht);
}
main.c
#include <stdio.h>
#include "hashtable.h"
int main()
{
hashtable_t *ht = ht_new(5);
printf("ADDING ELEMENTS ...\n");
ht_add(ht, 10, 1);
ht_add(ht, 20, 2);
ht_add(ht, 30, 3);
ht_add(ht, 40, 4);
ht_add(ht, 50, 5);
ht_add(ht, 60, 6);
ht_add(ht, 70, 7);
ht_add(ht, 80, 8);
ht_add(ht, 90, 9);
ht_add(ht, 100, 10);
ht_print(ht);
printf("DELETING ELEMENTS ...\n");
ht_delete(ht, 80);
ht_delete(ht, 70);
ht_delete(ht, 50);
ht_print(ht);
ht_free(ht);
return 0;
}