I started implementing my own hashmap in C after taking inspiration from Git's hashmap API. As far as I know, the closest thing to templates in C are macros. I've seen that they are used in uthash or khash single header files, but I don't know how and when to use them correctly. I'm currently stuck, my code is a mess, I rewrote it many times, I really don't know what to do. Any help would be appreciated
hashmap.h:
#ifndef HASHMAP_H
#define HASHMAP_H
#include <string.h>
typedef struct hashmap_entry {
void *key;
void *data;
struct hashmap_entry *next;
} hashmap_entry_t;
typedef unsigned int (*hashmap_hash_fn)(const void *key);
typedef int (*hashmap_cmp_fn)(const void *key1, const void *key2);
typedef struct {
hashmap_entry_t **table;
size_t size;
size_t count;
size_t grow_at;
size_t shrink_at;
hashmap_hash_fn hash_fn;
hashmap_cmp_fn cmp_fn;
} hashmap_t;
unsigned int fnv1a_hash_str(const void *key);
unsigned int fnv1a_hash_int(const void *key);
void hashmap_init(hashmap_t *map, size_t initial_size, hashmap_hash_fn hash_fn,
hashmap_cmp_fn cmp_fn);
void hashmap_release(hashmap_t *map);
int hashmap_add(hashmap_t *map, void *key, void *data);
void hashmap_rehash(hashmap_t *map, size_t newsize);
hashmap_entry_t *hashmap_get_entry(hashmap_t *map, const void *key);
void *hashmap_get(hashmap_t *map, const void *key);
void *hashmap_remove_entry(hashmap_t *map, hashmap_entry_t *entry);
void *hashmap_remove(hashmap_t *map, const void *key);
int hashmap_cleanremove(hashmap_t *map, const void *key);
static inline int hashmap_cmp_str(const void *key1, const void *key2) {
return strcmp((const char *)key1, (const char *)key2) == 0;
}
static inline int hashmap_cmp_int(const void *key1, const void *key2) {
return *(const int *)key1 == *(const int *)key2;
}
#endif
hashmap.c:
#include "hashmap.h"
#include <stdlib.h>
#include <string.h>
#define FNV32_PRIME 0x01000193
#define FNV32_BASE 0x811c9dc5
#define HASHMAP_INITIAL_SIZE 64
#define HASHMAP_LOAD_FACTOR 70
unsigned int fnv1a_hash_str(const void *key) {
unsigned int hash = FNV32_BASE;
for (const unsigned char *c = (const unsigned char *)key; *c; c++)
hash = (hash ^ *c) * FNV32_PRIME;
return hash;
}
unsigned int fnv1a_hash_int(const void *key) {
unsigned int hash = FNV32_BASE;
const unsigned char *b = (const unsigned char *)key;
for (size_t i = 0, size = sizeof(int); i < size; i++)
hash = (hash ^ b[i]) * FNV32_PRIME;
return hash;
}
static void alloc_table(hashmap_t *map, size_t size) {
memset(map, 0, sizeof(*map));
map->size = size;
map->table = calloc(size, sizeof(hashmap_entry_t *));
if (!map->table) {
free(map->table);
// die
}
map->grow_at = size * HASHMAP_LOAD_FACTOR / 100;
if (size > HASHMAP_INITIAL_SIZE)
map->shrink_at = map->grow_at / 3 + 1;
}
void hashmap_init(hashmap_t *map, size_t initial_size, hashmap_hash_fn hash_fn, hashmap_cmp_fn cmp_fn) {
if (!initial_size)
initial_size = HASHMAP_INITIAL_SIZE;
alloc_table(map, initial_size);
map->hash_fn = hash_fn;
map->cmp_fn = cmp_fn;
}
void hashmap_release(hashmap_t *map) {
for (size_t i = 0; i < map->size; i++) {
hashmap_entry_t *e = map->table[i];
while (e) {
hashmap_entry_t *next = e->next;
free(e->key);
free(e->data);
free(e);
e = next;
}
}
free(map->table);
}
static void hashmap_add_entry(hashmap_t *map, hashmap_entry_t *entry) {
size_t b = map->hash_fn(entry->key) % map->size;
entry->next = map->table[b];
map->table[b] = entry;
map->count++;
if (map->count == map->grow_at)
hashmap_rehash(map, map->size * 2);
}
int hashmap_add(hashmap_t *map, void *key, void *data) {
size_t b = map->hash_fn(key) % map->size;
hashmap_entry_t *e = map->table[b];
while (e) {
if (map->cmp_fn(e->key, key)) {
return 1;
}
e = e->next;
}
e = malloc(sizeof(hashmap_entry_t));
if (!e) {
free(e);
// die
}
memset(e, 0, sizeof(*e));
e->key = (void *)key;
e->data = data;
e->next = map->table[b];
map->table[b] = e;
map->count++;
if (map->count == map->grow_at)
hashmap_rehash(map, map->size * 2);
return 0;
}
void hashmap_rehash(hashmap_t *map, size_t newsize) {
size_t oldsize = map->size;
hashmap_entry_t **oldtable = map->table;
hashmap_hash_fn oldhash_fn = map->hash_fn;
hashmap_cmp_fn oldcmp_fn = map->cmp_fn;
alloc_table(map, newsize);
map->hash_fn = oldhash_fn;
map->cmp_fn = oldcmp_fn;
for (size_t i = 0; i < oldsize; i++) {
hashmap_entry_t *e = oldtable[i];
while (e) {
hashmap_entry_t *next = e->next;
e->next = NULL;
hashmap_add_entry(map, e);
e = next;
}
}
free(oldtable);
}
hashmap_entry_t *hashmap_get_entry(hashmap_t *map, const void *key) {
size_t b = map->hash_fn(key) % map->size;
hashmap_entry_t *e = map->table[b];
while (e) {
if (map->cmp_fn(e->key, key))
return e;
e = e->next;
}
return NULL;
}
void *hashmap_get(hashmap_t *map, const void *key) {
hashmap_entry_t *e = hashmap_get_entry(map, key);
return e ? e->data : NULL;
}
void *hashmap_remove_entry(hashmap_t *map, hashmap_entry_t *entry) {
void *data = entry->data;
free(entry->key);
free(entry);
entry = NULL;
map->count--;
if (map->shrink_at == map->count)
hashmap_rehash(map, map->size / 2);
return data;
}
void *hashmap_remove(hashmap_t *map, const void *key) {
size_t b = map->hash_fn(key) % map->size;
hashmap_entry_t *e = map->table[b];
hashmap_entry_t *prev = NULL;
while (e) {
if (map->cmp_fn(e->key, key)) {
if (prev)
prev->next = e->next;
else
map->table[b] = e->next;
return hashmap_remove_entry(map, e);
}
prev = e;
e = e->next;
}
return NULL;
}
int hashmap_cleanremove(hashmap_t *map, const void *key) {
void *data = hashmap_remove(map, key);
if (!data)
return 1;
free(data);
return 0;
}