#include #include /** * 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_NOT_FOUND; // 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; }