197 lines
6.6 KiB
C
197 lines
6.6 KiB
C
#include <c_LinearProbingHashST.h>
|
|
#include <c_Memory.h>
|
|
|
|
/**
|
|
* FNV-1a baseline string/scalar data hash scrambling algorithm.
|
|
*/
|
|
C_STATIC_FORCE_INLINE
|
|
uint32_t c_LPHash_DefaultHash(const void* key, c_size_t key_size) {
|
|
const uint8_t* data = (const uint8_t*)key;
|
|
uint32_t hash = 0x811C9DC5;
|
|
for (c_size_t i = 0; i < key_size; i++) {
|
|
hash ^= data[i];
|
|
hash *= 0x01000193;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
c_err_t c_LinearProbingHashST_Init(c_LinearProbingHashST_t* st, c_size_t initial_capacity,
|
|
c_size_t key_size, c_size_t val_size,
|
|
uint32_t (*hash_fn)(const void*, c_size_t),
|
|
int (*key_compar)(const void*, const void*)) {
|
|
if (st == NULL || initial_capacity == 0 || key_size == 0 || val_size == 0 || key_compar == NULL) {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
st->M = initial_capacity;
|
|
st->N = 0;
|
|
st->key_size = key_size;
|
|
st->val_size = val_size;
|
|
st->hash_fn = (hash_fn != NULL) ? hash_fn : c_LPHash_DefaultHash;
|
|
st->key_compar = key_compar;
|
|
|
|
st->keys = C_ALLOC(st->M * key_size);
|
|
st->vals = C_ALLOC(st->M * val_size);
|
|
st->occupied = (c_bool_t*)C_ALLOC(st->M * sizeof(c_bool_t));
|
|
|
|
if (st->keys == NULL || st->vals == NULL || st->occupied == NULL) {
|
|
C_FREE(st->keys); C_FREE(st->vals); C_FREE(st->occupied);
|
|
st->keys = NULL; st->vals = NULL; st->occupied = NULL;
|
|
return C_ERR_NOMEM;
|
|
}
|
|
|
|
memset(st->occupied, C_FALSE, st->M * sizeof(c_bool_t));
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
void c_LinearProbingHashST_Destroy(c_LinearProbingHashST_t* st) {
|
|
if (st) {
|
|
C_FREE(st->keys); st->keys = NULL;
|
|
C_FREE(st->vals); st->vals = NULL;
|
|
C_FREE(st->occupied); st->occupied = NULL;
|
|
st->M = 0;
|
|
st->N = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Explicit internal resizing routing handler.
|
|
* Essential for keeping the Load Factor (alpha) under 0.5 to prevent clustering.
|
|
*/
|
|
static c_err_t c_LinearProbingHashST_Resize(c_LinearProbingHashST_t* st, c_size_t capacity) {
|
|
c_LinearProbingHashST_t temp_st;
|
|
c_err_t err = c_LinearProbingHashST_Init(&temp_st, capacity, st->key_size, st->val_size, st->hash_fn, st->key_compar);
|
|
if (err != C_ERR_OK) return err;
|
|
|
|
char* keys_base = (char*)st->keys;
|
|
char* vals_base = (char*)st->vals;
|
|
c_size_t ks = st->key_size;
|
|
c_size_t vs = st->val_size;
|
|
|
|
// Rehash and insert all existing active items into the new, expanded table footprint
|
|
for (c_size_t i = 0; i < st->M; i++) {
|
|
if (st->occupied[i]) {
|
|
extern c_err_t c_LinearProbingHashST_Put(c_LinearProbingHashST_t*, const void*, const void*);
|
|
err = c_LinearProbingHashST_Put(&temp_st, keys_base + (i * ks), vals_base + (i * vs));
|
|
if (err != C_ERR_OK) {
|
|
c_LinearProbingHashST_Destroy(&temp_st);
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Swap parameters to apply the newly rehashed table context
|
|
C_FREE(st->keys); C_FREE(st->vals); C_FREE(st->occupied);
|
|
st->keys = temp_st.keys;
|
|
st->vals = temp_st.vals;
|
|
st->occupied = temp_st.occupied;
|
|
st->M = temp_st.M;
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
c_err_t c_LinearProbingHashST_Put(c_LinearProbingHashST_t* st, const void* key, const void* val) {
|
|
if (st == NULL || key == NULL || val == NULL) return C_ERR_PARAM;
|
|
|
|
// Enforce an upper bound load factor limit of 50% to mitigate clustering degradation
|
|
if (st->N >= st->M / 2) {
|
|
c_err_t err = c_LinearProbingHashST_Resize(st, st->M * 2);
|
|
if (err != C_ERR_OK) return err;
|
|
}
|
|
|
|
char* keys_base = (char*)st->keys;
|
|
char* vals_base = (char*)st->vals;
|
|
c_size_t ks = st->key_size;
|
|
c_size_t vs = st->val_size;
|
|
|
|
c_size_t i;
|
|
for (i = st->hash_fn(key, ks) % st->M; st->occupied[i]; i = (i + 1) % st->M) {
|
|
if (st->key_compar(keys_base + (i * ks), key) == 0) {
|
|
// Found existing key match: update values block payload in place
|
|
memcpy(vals_base + (i * vs), val, vs);
|
|
return C_ERR_OK;
|
|
}
|
|
}
|
|
|
|
// Insert new item into the available open slot found by probing
|
|
memcpy(keys_base + (i * ks), key, ks);
|
|
memcpy(vals_base + (i * vs), val, vs);
|
|
st->occupied[i] = C_TRUE;
|
|
st->N++;
|
|
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
void* c_LinearProbingHashST_Get(const c_LinearProbingHashST_t* st, const void* key) {
|
|
if (st == NULL || st->keys == NULL || key == NULL) return NULL;
|
|
|
|
char* keys_base = (char*)st->keys;
|
|
c_size_t ks = st->key_size;
|
|
|
|
for (c_size_t i = st->hash_fn(key, ks) % st->M; st->occupied[i]; i = (i + 1) % st->M) {
|
|
if (st->key_compar(keys_base + (i * ks), key) == 0) {
|
|
return (char*)st->vals + (i * st->val_size);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
c_bool_t c_LinearProbingHashST_Contains(const c_LinearProbingHashST_t* st, const void* key) {
|
|
return c_LinearProbingHashST_Get(st, key) != NULL;
|
|
}
|
|
|
|
c_err_t c_LinearProbingHashST_Delete(c_LinearProbingHashST_t* st, const void* key) {
|
|
if (st == NULL || key == NULL) return C_ERR_PARAM;
|
|
|
|
char* keys_base = (char*)st->keys;
|
|
c_size_t ks = st->key_size;
|
|
c_size_t vs = st->val_size;
|
|
|
|
c_size_t i = st->hash_fn(key, ks) % st->M;
|
|
while (st->occupied[i]) {
|
|
if (st->key_compar(keys_base + (i * ks), key) == 0) {
|
|
break;
|
|
}
|
|
i = (i + 1) % st->M;
|
|
}
|
|
|
|
// Key to delete was not found in the hash table
|
|
if (!st->occupied[i]) return C_ERR_NOTFOUND;
|
|
|
|
// Hard delete: Free the targeted slot index flag
|
|
st->occupied[i] = C_FALSE;
|
|
st->N--;
|
|
|
|
// CRITICAL REQUIREMENT: Rehash all subsequent cluster elements
|
|
// to bridge the open slot gap caused by deletion, preventing future search short-circuits.
|
|
i = (i + 1) % st->M;
|
|
while (st->occupied[i]) {
|
|
// Capture old keys/values payload allocations locally
|
|
void* key_to_rehash = C_ALLOC(ks);
|
|
void* val_to_rehash = C_ALLOC(vs);
|
|
memcpy(key_to_rehash, keys_base + (i * ks), ks);
|
|
memcpy(val_to_rehash, (char*)st->vals + (i * vs), vs);
|
|
|
|
// Explicitly clear the current cluster entry tracking variables
|
|
st->occupied[i] = C_FALSE;
|
|
st->N--;
|
|
|
|
// Re-insert the captured element into the table using standard routing rules
|
|
c_LinearProbingHashST_Put(st, key_to_rehash, val_to_rehash);
|
|
|
|
C_FREE(key_to_rehash);
|
|
C_FREE(val_to_rehash);
|
|
|
|
i = (i + 1) % st->M;
|
|
}
|
|
|
|
// Shrink the table capacity automatically if utilization drops below 12.5%
|
|
if (st->N > 0 && st->N <= st->M / 8) {
|
|
c_LinearProbingHashST_Resize(st, st->M / 2);
|
|
}
|
|
|
|
return C_ERR_OK;
|
|
}
|