Files
hm.h/hm.h
2025-11-18 21:43:44 +01:00

545 lines
13 KiB
C

// hm.h - v1.0.0 - MIT License
// chained hash table implementation as a single header library.
#ifndef HM_H_
#define HM_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#ifndef HM_INITIAL_CAPACITY
# define HM_INITIAL_CAPACITY 256
#endif // HM_INITIAL_CAPACITY
#ifndef HM_MAX_LOAD_FACTOR
# define HM_MAX_LOAD_FACTOR 0.75f
#endif // HM_MAX_LOAD_FACTOR
#ifndef HM_GROWTH_FACTOR
# define HM_GROWTH_FACTOR 2
#endif // HM_GROWTH_FACTOR
#ifndef HM_NO_ASSERT
# ifndef HM_ASSERT
# include <assert.h>
# define HM_ASSERT assert
# endif // HM_ASSERT
#else
# define HM_ASSERT(...) ((void)0)
#endif // HM_NO_ASSERT
#ifndef HM_REALLOC
# include <stdlib.h>
# define HM_REALLOC realloc
#endif // HM_REALLOC
#ifndef HM_FREE
# include <stdlib.h>
# define HM_FREE free
#endif // HM_FREE
#ifdef __cplusplus
extern "C" { // Prevent name mangling
#endif // __cplusplus
typedef struct Hm__Bucket {
struct Hm__Bucket *next;
struct Hm__Bucket *prev;
void *key;
void *value;
size_t value_size;
} Hm__Bucket;
typedef struct {
Hm__Bucket **map;
size_t count;
size_t capacity;
size_t key_size;
size_t value_size;
} HashMap;
typedef struct {
const HashMap *hm;
Hm__Bucket *bucket;
size_t index;
bool end;
} HashMapIterator;
// Create and free
HashMap hm_create(size_t key_size, size_t value_size);
void hm_free(HashMap *hm);
// Modify, access and remove
bool hm_put(HashMap *hm, const void *key, const void *value);
void *hm_get(const HashMap *hm, const void *key);
bool hm_remove(HashMap *hm, const void *key);
// Iterator functions
HashMapIterator hm_iterate(const HashMap *hm);
const void *hm_key(const HashMapIterator *it);
void *hm_value(const HashMapIterator *it);
void *hm_next(HashMapIterator *it);
// Private functions
uint64_t hm__fnv1a(const void *buffer, size_t size);
size_t hm__key_size(const HashMap *hm, const void *key);
size_t hm__value_size(const HashMap *hm, const void *value);
bool hm__keycmp(const HashMap *hm, const void *key1, const void *key2);
bool hm__rehash(HashMap *hm);
Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *value, size_t value_size);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // HM_H_
#ifdef HM_IMPLEMENTATION
#ifdef __cplusplus
extern "C" { // Prevent name mangling
#endif // __cplusplus
HashMap hm_create(size_t key_size, size_t value_size)
{
HashMap hm;
memset(&hm, 0, sizeof(hm));
hm.capacity = HM_INITIAL_CAPACITY;
hm.key_size = key_size;
hm.value_size = value_size;
return hm;
}
void hm_free(HashMap *hm)
{
if (hm == NULL || hm->map == NULL) {
memset(hm, 0, sizeof(*hm));
return;
}
for (size_t i = 0; i < hm->capacity; ++i) {
Hm__Bucket *cur = hm->map[i];
if (cur == NULL) {
continue;
}
while (cur != NULL) {
Hm__Bucket *next = cur->next;
HM_FREE(cur->key);
HM_FREE(cur->value);
HM_FREE(cur);
cur = next;
}
}
HM_FREE(hm->map);
memset(hm, 0, sizeof(*hm));
}
bool hm_put(HashMap *hm, const void *key, const void *value)
{
if (hm == NULL || key == NULL || value == NULL) {
HM_ASSERT(false && "Invalid parameters");
return false;
}
// Ensure that HashMap is initialised
if (hm->map == NULL) {
hm->map = (Hm__Bucket**)HM_REALLOC(NULL, sizeof(*hm->map) * HM_INITIAL_CAPACITY);
if (hm->map == NULL) {
HM_ASSERT(false && "Reallocation failed");
return false;
}
hm->capacity = HM_INITIAL_CAPACITY;
memset(hm->map, 0, sizeof(*hm->map) * hm->capacity);
}
// Rehash if necessary
if ((float)hm->count + 1 > (float)hm->capacity * HM_MAX_LOAD_FACTOR) {
if (!hm__rehash(hm)) {
return false;
}
}
// Key-value sizes
size_t key_size = hm__key_size(hm, key);
size_t value_size = hm__value_size(hm, value);
// Get the head bucket
size_t idx = hm__fnv1a(key, key_size) % hm->capacity;
Hm__Bucket *head = hm->map[idx];
// Check if key already exists
for (Hm__Bucket *cur = head; cur != NULL; cur = cur->next) {
if (!hm__keycmp(hm, key, cur->key)) {
continue;
}
// Fixed or equal value_size
if (hm->value_size != 0 || value_size == cur->value_size) {
memcpy(cur->value, value, value_size);
return true;
}
// Temporary buffer in case of overlapping pointers
uint8_t *tmp = (uint8_t*)HM_REALLOC(NULL, value_size);
if (tmp == NULL) {
HM_ASSERT(false && "Reallocation failed");
return false;
}
memcpy(tmp, value, value_size);
// Realloc and copy
uint8_t *new_value = (uint8_t*)HM_REALLOC(cur->value, value_size);
if (new_value == NULL) {
HM_ASSERT(false && "Reallocation failed");
return false;
}
cur->value = new_value;
memcpy(cur->value, tmp, value_size);
HM_FREE(tmp);
return true;
}
// Insert the new value
Hm__Bucket *new_bucket = hm__bucket_create(key, key_size, value, value_size);
if (new_bucket == NULL) {
return false;
}
new_bucket->next = head;
if (head != NULL) {
head->prev = new_bucket;
}
hm->map[idx] = new_bucket;
++hm->count;
return true;
}
void *hm_get(const HashMap *hm, const void *key)
{
if (hm == NULL) {
HM_ASSERT(false && "Invalid parameters");
return NULL;
}
if (hm->map == NULL || key == NULL) {
return NULL;
}
size_t key_size = hm__key_size(hm, key);
size_t idx = hm__fnv1a(key, key_size) % hm->capacity;
Hm__Bucket *head = hm->map[idx];
if (head == NULL) {
return NULL;
}
for (Hm__Bucket *cur = head; cur != NULL; cur = cur->next) {
if (hm__keycmp(hm, key, cur->key)) {
return cur->value;
}
}
return NULL;
}
bool hm_remove(HashMap *hm, const void *key)
{
if (hm == NULL || hm->map == NULL || key == NULL) {
return false;
}
size_t key_size = hm__key_size(hm, key);
size_t idx = hm__fnv1a(key, key_size) % hm->capacity;
Hm__Bucket *head = hm->map[idx];
if (head == NULL) {
return false;
}
for (Hm__Bucket *cur = head; cur != NULL; cur = cur->next) {
if (!hm__keycmp(hm, key, cur->key)) {
continue;
}
if (cur == head) {
hm->map[idx] = cur->next;
} else {
cur->prev->next = cur->next;
}
if (cur->next != NULL) {
cur->next->prev = cur->prev;
}
HM_FREE(cur->key);
HM_FREE(cur->value);
HM_FREE(cur);
--hm->count;
return true;
}
return false;
}
HashMapIterator hm_iterate(const HashMap *hm)
{
HashMapIterator it;
memset(&it, 0, sizeof(it));
if (hm == NULL) {
HM_ASSERT(false && "Invalid parameters");
}
it.hm = hm;
return it;
}
const void *hm_key(const HashMapIterator *it)
{
if (it == NULL || it->hm == NULL) {
HM_ASSERT(false && "Invalid parameters");
return NULL;
}
// Reached the end
if (it->end) {
return NULL;
}
return it->bucket->key;
}
void *hm_value(const HashMapIterator *it)
{
if (it == NULL || it->hm == NULL) {
HM_ASSERT(false && "Invalid parameters");
return NULL;
}
// Reached the end
if (it->end) {
return NULL;
}
return it->bucket->value;
}
void *hm_next(HashMapIterator *it)
{
if (it == NULL || it->hm == NULL) {
HM_ASSERT(false && "Invalid parameters");
return NULL;
}
// Reached the end
if (it->end) {
return NULL;
}
if (it->bucket == NULL) {
// Find first list of buckets
while ((it->bucket = it->hm->map[it->index++]) == NULL);
return it->bucket->value;
}
// Next bucket
if (it->bucket->next != NULL) {
it->bucket = it->bucket->next;
return it->bucket->value;
}
// Find next list of buckets
while ((it->bucket = it->hm->map[++it->index]) == NULL);
// Reached the end
if (it->index >= it->hm->capacity) {
it->end = true;
return NULL;
}
it->bucket = it->hm->map[it->index];
return it->bucket->value;
}
uint64_t hm__fnv1a(const void *buffer, size_t size)
{
if (buffer == NULL) {
HM_ASSERT(false && "Invalid parameters");
return 0;
}
const uint8_t *b = (const uint8_t*)buffer;
uint64_t hash = 14695981039346656037ULL;
for (size_t i = 0; i < size; ++i) {
hash ^= b[i];
hash *= 1099511628211ULL;
}
return hash;
}
size_t hm__key_size(const HashMap *hm, const void *key)
{
if (hm == NULL || key == NULL) {
HM_ASSERT(false && "Invalid parameters");
return 0;
}
return (hm->key_size == 0 ? strlen((const char*)key) + 1 : hm->key_size);
}
size_t hm__value_size(const HashMap *hm, const void *value)
{
if (hm == NULL || value == NULL) {
HM_ASSERT(false && "Invalid parameters");
return 0;
}
return (hm->value_size == 0 ? strlen((const char*)value) + 1 : hm->value_size);
}
bool hm__keycmp(const HashMap *hm, const void *key1, const void *key2)
{
if (hm == NULL || key1 == NULL || key2 == NULL) {
HM_ASSERT(false && "Invalid parameters");
return false;
}
if (hm->key_size == 0) {
return strcmp((const char*)key1, (const char*)key2) == 0;
}
return memcmp(key1, key2, hm->key_size) == 0;
}
bool hm__rehash(HashMap *hm)
{
if (hm == NULL || hm->map == NULL) {
HM_ASSERT(false && "Invalid parameters");
return false;
}
// Allocate new table
size_t new_capacity = hm->capacity * HM_GROWTH_FACTOR;
Hm__Bucket **new_map = (Hm__Bucket**)HM_REALLOC(NULL, sizeof(*new_map) * new_capacity);
if (new_map == NULL) {
HM_ASSERT(false && "Reallocation failed");
return false;
}
memset(new_map, 0, sizeof(*new_map) * new_capacity);
// Iterate old table
for (size_t i = 0; i < hm->capacity; ++i) {
Hm__Bucket *cur = hm->map[i];
while (cur != NULL) {
Hm__Bucket *next = cur->next;
size_t key_size = hm__key_size(hm, cur->key);
uint64_t idx = hm__fnv1a(cur->key, key_size) % new_capacity;
// Insert new bucket
cur->prev = NULL;
cur->next = new_map[idx];
if (new_map[idx] != NULL) {
new_map[idx]->prev = cur;
}
new_map[idx] = cur;
cur = next;
}
}
HM_FREE(hm->map);
hm->map = new_map;
hm->capacity = new_capacity;
return true;
}
Hm__Bucket *hm__bucket_create(const void *key, size_t key_size, const void *value, size_t value_size)
{
if (key == NULL || value == NULL) {
HM_ASSERT(false && "Invalid parameters");
return NULL;
}
// Allocate bucket
Hm__Bucket *bucket = (Hm__Bucket*)HM_REALLOC(NULL, sizeof(*bucket));
if (bucket == NULL) {
HM_ASSERT(false && "Reallocation failed");
return NULL;
}
memset(bucket, 0, sizeof(*bucket));
// Allocate key
bucket->key = HM_REALLOC(NULL, key_size);
if (bucket->key == NULL) {
HM_ASSERT(false && "Reallocation failed");
HM_FREE(bucket);
return NULL;
}
memset(bucket->key, 0, key_size);
// Allocate value
bucket->value = HM_REALLOC(NULL, value_size);
if (bucket->value == NULL) {
HM_ASSERT(false && "Reallocation failed");
HM_FREE(bucket->key);
HM_FREE(bucket);
return NULL;
}
memset(bucket->value, 0, value_size);
// Copy Key-value data
memcpy(bucket->key, key, key_size);
memcpy(bucket->value, value, value_size);
bucket->value_size = value_size;
return bucket;
}
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // HM_IMPLEMENTATION
/*
* Revision history:
*
* 1.0.0 (2025-11-18) Initial release
*/
/*
* MIT License
*
* Copyright (c) 2025 seajee
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/