650 lines
22 KiB
C
Executable File
650 lines
22 KiB
C
Executable File
// #include <stdint.h>
|
|
// #include <stdbool.h>
|
|
// #include <stdlib.h>
|
|
// #include <string.h>
|
|
// #include <stdio.h>
|
|
|
|
// #include "guf_common.h"
|
|
// #include "guf_dict.h"
|
|
|
|
// /*
|
|
// FNV-1a (32 bit) hash function.
|
|
// Generally, you should always call csr_hash with GUF_HASH_INIT as the hash argument, unless you want to create "chains" of hashes.
|
|
// cf. http://www.isthe.com/chongo/tech/comp/fnv/ (last retrieved: 2023-11-30)
|
|
// */
|
|
|
|
// uint32_t guf_hash32(const void *data, size_t num_bytes, uint32_t hash)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(data);
|
|
// const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
|
|
// const uint32_t FNV_32_PRIME = 16777619ul;
|
|
|
|
// for (size_t i = 0; i < num_bytes; ++i) {
|
|
// hash ^= data_bytes[i];
|
|
// hash *= FNV_32_PRIME;
|
|
// }
|
|
// return hash;
|
|
// }
|
|
|
|
// uint64_t guf_hash64(const void *data, size_t num_bytes, uint64_t hash)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(data);
|
|
// const unsigned char *data_bytes = (const unsigned char*)data; // This does not break strict-aliasing rules I think...
|
|
// const uint64_t FNV_64_PRIME = 1099511628211ull;
|
|
|
|
// for (size_t i = 0; i < num_bytes; ++i) {
|
|
// hash ^= data_bytes[i];
|
|
// hash *= FNV_64_PRIME;
|
|
// }
|
|
|
|
// return hash;
|
|
// }
|
|
|
|
// static inline size_t find_next_power_of_two(size_t num)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(num > 0);
|
|
// size_t pof2 = 1;
|
|
// while (pof2 < num) {
|
|
// GUF_ASSERT_RELEASE(pof2 * 2 > pof2);
|
|
// pof2 *= 2;
|
|
// }
|
|
// return pof2;
|
|
// }
|
|
|
|
// static const guf_dict_kv_id KV_ID_NULL = GUF_DICT_HASH_MAX;
|
|
// static const guf_dict_kv_id KV_ID_TOMBSTONE = GUF_DICT_HASH_MAX - 1;
|
|
// static const guf_dict_kv_id KV_ID_MAX = GUF_DICT_HASH_MAX - 2;
|
|
|
|
// const guf_dict GUF_DICT_UNINITIALISED = {
|
|
// .capacity_kv_status = 0,
|
|
// .size = 0,
|
|
// .num_tombstones = 0,
|
|
// .keys = NULL,
|
|
// .vals = NULL,
|
|
// .val_funcs = {.eq = NULL, .hash = NULL, .cpy = NULL, .move = NULL, .free = NULL, .type_size = 0},
|
|
// .kv_status = NULL,
|
|
// .probe_t = 0,
|
|
// .max_load_fac_fx10 = 0,
|
|
// .max_probelen = 0,
|
|
// };
|
|
|
|
// static inline void *cpy_key(guf_dict *ht, void *dst, const void *src, bool default_cpy)
|
|
// {
|
|
// if (default_cpy || ht->key_funcs.cpy == NULL) { // Default copy.
|
|
// return memcpy(dst, src, ht->key_funcs.type_size);
|
|
// } else {
|
|
// return ht->key_funcs.cpy(dst, src);
|
|
// }
|
|
// }
|
|
|
|
// static inline void *cpy_val(guf_dict *ht, void *dst, const void *src, bool default_cpy)
|
|
// {
|
|
// if (dst == NULL || src == NULL) {
|
|
// GUF_ASSERT_RELEASE(ht->val_funcs.type_size == 0);
|
|
// return NULL;
|
|
// }
|
|
// if (default_cpy || ht->val_funcs.cpy == NULL) { // Default copy.
|
|
// return memcpy(dst, src, ht->val_funcs.type_size);
|
|
// } else {
|
|
// return ht->val_funcs.cpy(dst, src);
|
|
// }
|
|
// }
|
|
|
|
// static inline void *cpy_or_move_val(guf_dict *ht, void *dst, void *src, guf_dict_insert_opt opts)
|
|
// {
|
|
// if (dst == NULL || src == NULL) {
|
|
// GUF_ASSERT_RELEASE(ht->val_funcs.type_size == 0);
|
|
// return NULL;
|
|
// }
|
|
// if ((opts & GUF_DICT_MOVE_VAL)) {
|
|
// GUF_ASSERT_RELEASE(ht->val_funcs.move);
|
|
// return ht->val_funcs.move(dst, src);
|
|
// } else { // Default copy.
|
|
// return cpy_val(ht, dst, src, false);
|
|
// }
|
|
// }
|
|
|
|
// static inline void *cpy_or_move_key(guf_dict *ht, void *dst, void *src, guf_dict_insert_opt opts)
|
|
// {
|
|
// if ((opts & GUF_DICT_MOVE_KEY)) {
|
|
// GUF_ASSERT_RELEASE(ht->key_funcs.move);
|
|
// return ht->key_funcs.move(dst, src);
|
|
// } else {
|
|
// return cpy_key(ht, dst, src, false);
|
|
// }
|
|
// }
|
|
|
|
// static inline guf_hash_size_t key_hash(const guf_dict *ht, const void *key)
|
|
// {
|
|
// if (ht->key_funcs.hash) {
|
|
// return ht->key_funcs.hash(key);
|
|
// } else { // Default hash function.
|
|
// return guf_hash(key, ht->key_funcs.type_size, GUF_HASH_INIT);
|
|
// }
|
|
// }
|
|
|
|
|
|
// static inline bool kv_stat_occupied(guf_dict_kv_status kv_stat) {
|
|
// return kv_stat.kv_id != KV_ID_NULL && kv_stat.kv_id != KV_ID_TOMBSTONE;
|
|
// }
|
|
|
|
|
|
|
|
// static void *key_get(guf_dict *ht, guf_dict_kv_id idx)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(idx <= KV_ID_MAX);
|
|
// char *ptr = (char*)ht->keys;
|
|
// return ptr + idx * ht->key_funcs.type_size;
|
|
|
|
// }
|
|
|
|
// static void *val_get(guf_dict *ht, guf_dict_kv_id idx)
|
|
// {
|
|
// if (ht->val_funcs.type_size == 0) {
|
|
// return NULL;
|
|
// }
|
|
// GUF_ASSERT_RELEASE(idx <= KV_ID_MAX);
|
|
|
|
// char *ptr = (char*)ht->vals;
|
|
// return ptr + idx * ht->val_funcs.type_size ;
|
|
// }
|
|
|
|
// static inline bool key_eq(guf_dict *ht, const void *key_a, const void *key_b)
|
|
// {
|
|
// if (ht->key_funcs.eq) {
|
|
// return ht->key_funcs.eq(key_a, key_b);
|
|
// } else { // Default equality function.
|
|
// return 0 == memcmp(key_a, key_b, ht->key_funcs.type_size);
|
|
// }
|
|
// }
|
|
|
|
// static inline bool key_eq_at(guf_dict *ht, size_t idx, const void *key, guf_hash_size_t hash_of_key)
|
|
// {
|
|
// GUF_ASSERT(idx < ht->capacity_kv_status);
|
|
// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx]));
|
|
|
|
// if (ht->kv_status[idx].k_hash == hash_of_key) { // Hashes match -> we check if the keys are actually equal.
|
|
// return key_eq(ht, key_get(ht, ht->kv_status[idx].kv_id), key);
|
|
// } else {
|
|
// // Hashes don't match -> early exit (we save a memory load from ht->kv_elems).
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
// static inline size_t probe_offset(size_t probe_len, guf_dict_probe_type probe_t)
|
|
// {
|
|
// GUF_ASSERT(probe_len > 0);
|
|
// switch (probe_t)
|
|
// {
|
|
// case GUF_DICT_PROBE_LINEAR:
|
|
// default:
|
|
// return 1;
|
|
// case GUF_DICT_PROBE_QUADRATIC:
|
|
// /*
|
|
// Guaranteed to visit each index once for capacities which are powers of two.
|
|
// cf. https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ (last-retrieved 2024-07-29)
|
|
// */
|
|
// return probe_len * (probe_len + 1) / 2; // 1, 3, 6, 10, 15, ... (starting from probe_len == 1)
|
|
// }
|
|
// }
|
|
|
|
// static inline size_t mod_pow2(size_t a, size_t b) // a mod b (with b being a power of two.)
|
|
// {
|
|
// GUF_ASSERT(b > 0);
|
|
// return a & (b - 1);
|
|
// }
|
|
|
|
// static size_t find_idx(guf_dict *ht, const void *key, bool *key_exists, bool find_first_free)
|
|
// {
|
|
// const guf_hash_size_t hash = key_hash(ht, key);
|
|
// size_t idx = mod_pow2(hash, ht->capacity_kv_status); // hash % ht->capacity
|
|
// const size_t start_idx = idx;
|
|
// size_t probe_len = 1;
|
|
// size_t first_tombstone_idx = SIZE_MAX;
|
|
// do {
|
|
// if (ht->kv_status[idx].kv_id == KV_ID_NULL) { // 1.) Empty.
|
|
// if (first_tombstone_idx != SIZE_MAX) {
|
|
// idx = first_tombstone_idx;
|
|
// }
|
|
// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen);
|
|
// GUF_ASSERT(!kv_stat_occupied(ht->kv_status[idx]));
|
|
// *key_exists = false;
|
|
// return idx;
|
|
// } else if (ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE) { // 2.) Tombstone.
|
|
// if (first_tombstone_idx == SIZE_MAX) {
|
|
// first_tombstone_idx = idx;
|
|
// }
|
|
// if (find_first_free) {
|
|
// goto end;
|
|
// } else {
|
|
// goto probe;
|
|
// }
|
|
// } else if (key_eq_at(ht, idx, key, hash)) { // 3.) Key already exists.
|
|
// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen);
|
|
// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx]));
|
|
// *key_exists = true;
|
|
// return idx;
|
|
// } else { // 4.) Have to probe due to hash-collision (idx is already occupied, but not by the key).
|
|
// probe:
|
|
// idx = mod_pow2(idx + probe_offset(probe_len, ht->probe_t), ht->capacity_kv_status);
|
|
// ++probe_len;
|
|
// GUF_ASSERT_RELEASE(probe_len < UINT32_MAX);
|
|
// }
|
|
// } while (idx != start_idx);
|
|
|
|
// end:
|
|
// if (first_tombstone_idx != SIZE_MAX) { // Edge case: No empty slots, but found tombstone.
|
|
// ht->max_probelen = GUF_MAX(probe_len, ht->max_probelen);
|
|
// GUF_ASSERT(!kv_stat_occupied(ht->kv_status[first_tombstone_idx]));
|
|
// *key_exists = false;
|
|
// return first_tombstone_idx;
|
|
// }
|
|
|
|
// *key_exists = false;
|
|
// return SIZE_MAX; // Failed to find an idx.
|
|
// }
|
|
|
|
// static void insert_kv(guf_dict *ht, void *key, void *val, size_t idx, guf_dict_insert_opt opts, bool default_cpy_key, bool default_cpy_val)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(idx < ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(ht->kv_status[idx].kv_id == KV_ID_NULL || ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE);
|
|
// GUF_ASSERT_RELEASE(!kv_stat_occupied(ht->kv_status[idx]));
|
|
|
|
// if (!default_cpy_key) {
|
|
// if (!cpy_or_move_key(ht, key_get(ht, idx), key, opts)) {
|
|
// cpy_key(ht, key_get(ht, idx), key, true);
|
|
// }
|
|
// } else {
|
|
// cpy_key(ht, key_get(ht, idx), key, true);
|
|
// }
|
|
// if (!default_cpy_val) {
|
|
// if (!cpy_or_move_val(ht, val_get(ht, idx), val, opts)) {
|
|
// cpy_val(ht, val_get(ht, idx), val, true);
|
|
// }
|
|
// } else {
|
|
// cpy_val(ht, val_get(ht, idx), val, true);
|
|
// }
|
|
|
|
// if (ht->kv_status[idx].kv_id == KV_ID_TOMBSTONE) {
|
|
// GUF_ASSERT_RELEASE(ht->num_tombstones > 0);
|
|
// --ht->num_tombstones;
|
|
// }
|
|
|
|
// ht->kv_status[idx].k_hash = key_hash(ht, key_get(ht, idx));
|
|
// ++ht->size;
|
|
// }
|
|
|
|
// static void update_v(guf_dict *ht, void *val, size_t idx, guf_dict_insert_opt opts)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(idx < ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(kv_stat_occupied(ht->kv_status[idx]));
|
|
|
|
// if (ht->val_funcs.free) {
|
|
// ht->val_funcs.free(val_get(ht, idx));
|
|
// }
|
|
// cpy_or_move_val(ht, val_get(ht, idx), val, opts);
|
|
// }
|
|
|
|
// bool guf_dict_init(guf_dict *ht, size_t start_capacity, const guf_dict_kv_funcs *key_funcs, const guf_dict_kv_funcs *val_funcs)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(ht);
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status == 0 && ht->size == 0 && ht->num_tombstones == 0 && ht->max_probelen == 0);
|
|
// GUF_ASSERT_RELEASE(ht->keys == NULL && ht->vals == NULL);
|
|
|
|
// GUF_ASSERT_RELEASE(key_funcs && val_funcs);
|
|
// ht->key_funcs = *key_funcs;
|
|
// ht->val_funcs = *val_funcs;
|
|
|
|
// if (val_funcs->type_size == 0) {
|
|
// // TODO: is a set!
|
|
// }
|
|
// if (start_capacity < 1) {
|
|
// start_capacity = 1;
|
|
// }
|
|
|
|
// ht->capacity_kv_status = find_next_power_of_two(start_capacity);
|
|
// ht->size = ht->num_tombstones = 0;
|
|
// ht->max_probelen = 0;
|
|
|
|
// GUF_ASSERT_RELEASE(ht->key_funcs.type_size > 0);
|
|
// // GUF_ASSERT_RELEASE(ht->val_funcs.type_size > 0);
|
|
|
|
// ht->probe_t = GUF_DICT_PROBE_QUADRATIC;
|
|
// ht->max_load_fac_fx10 = GUF_DICT_MAX_LOAD_FAC_FX10_DEFAULT;
|
|
// GUF_ASSERT_RELEASE(ht->max_load_fac_fx10 != 0 && ht->max_load_fac_fx10 <= 1024);
|
|
|
|
// ht->keys = calloc(ht->capacity_kv_status, ht->val_funcs.type_size);
|
|
// if (!ht->keys) {
|
|
// return false;
|
|
// }
|
|
|
|
// ht->vals = NULL;
|
|
// if (ht->val_funcs.type_size > 0) {
|
|
// ht->vals = calloc(ht->capacity_kv_status, ht->val_funcs.type_size);
|
|
// if (!ht->vals) {
|
|
// free(ht->keys);
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
// ht->kv_status = calloc(ht->capacity_kv_status, sizeof(uint8_t));
|
|
// if (!ht->kv_status) {
|
|
// free(ht->keys);
|
|
// free(ht->vals);
|
|
// return false;
|
|
// }
|
|
// for (size_t i = 0; i < ht->capacity_kv_status; ++i) {
|
|
// GUF_ASSERT(ht->kv_status[i].kv_id == KV_ID_NULL);
|
|
// }
|
|
|
|
// return ht;
|
|
// }
|
|
|
|
// bool guf_dict_insert(guf_dict *ht, void *key, void *val, guf_dict_insert_opt opts)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(ht);
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status);
|
|
|
|
// if ((opts & GUF_DICT_MOVE_KEY) && ht->key_funcs.move == NULL) {
|
|
// // Ignore -Wunused-value.
|
|
// fprintf(stderr, "guf_dict_insert: key_funcs.move is NULL while GUF_DICT_MOVE_KEY is set\n");
|
|
// GUF_ASSERT(false);
|
|
// return false;
|
|
// }
|
|
// if ((opts & GUF_DICT_MOVE_VAL) && ht->val_funcs.move == NULL) {
|
|
// // Ignore -Wunused-value.
|
|
// fprintf(stderr, "guf_dict_insert: val_funcs.move is NULL while GUF_DICT_MOVE_VAL is set\n");
|
|
// GUF_ASSERT(false);
|
|
// return false;
|
|
// }
|
|
|
|
// if (guf_dict_load_factor_fx10(ht) > ht->max_load_fac_fx10 || ht->capacity_kv_status == ht->size) { // Handle growth:
|
|
// const size_t new_cap = 2 * ht->capacity_kv_status; // TODO: Limit to MAX_CAPACITY
|
|
// bool overflow = new_cap <= ht->capacity_kv_status;
|
|
// GUF_ASSERT(!overflow);
|
|
// if (overflow) {
|
|
// return false;
|
|
// }
|
|
// void *new_keys = calloc(new_cap, ht->key_funcs.type_size);
|
|
// if (!new_keys) {
|
|
// return false;
|
|
// }
|
|
|
|
// void *new_vals = NULL;
|
|
// if (ht->val_funcs.type_size > 0) {
|
|
// new_vals = calloc(new_cap, ht->val_funcs.type_size);
|
|
// if (!new_vals) {
|
|
// free(new_keys);
|
|
// return false;
|
|
// }
|
|
// } else {
|
|
// GUF_ASSERT_RELEASE(ht->vals == NULL);
|
|
// }
|
|
|
|
// guf_dict_kv_status *new_kv_status = calloc(new_cap, sizeof(guf_dict_kv_status)); // No realloc here!
|
|
// if (!new_kv_status) {
|
|
// free(new_keys);
|
|
// free(new_vals);
|
|
// return false;
|
|
// }
|
|
// for (size_t i = 0; i < new_cap; ++i) {
|
|
// new_kv_status[i].kv_id = KV_ID_NULL;
|
|
// new_kv_status[i].k_hash = 0;
|
|
// }
|
|
|
|
// guf_dict ht_new = *ht;
|
|
// ht_new.kv_status = new_kv_status;
|
|
// ht_new.keys = new_keys;
|
|
// ht_new.vals = new_vals;
|
|
// ht_new.size = 0;
|
|
// ht_new.capacity_kv_status = new_cap;
|
|
// ht_new.max_probelen = 0;
|
|
|
|
// size_t new_size = 0;
|
|
// for (size_t i = 0; i < ht->capacity_kv_status; ++i) {
|
|
// if (kv_stat_occupied(ht->kv_status[i])) {
|
|
// bool key_exists = false;
|
|
// const size_t new_idx = find_idx(&ht_new, key_get(ht, i), &key_exists, true);
|
|
// GUF_ASSERT_RELEASE(new_idx != SIZE_MAX);
|
|
// GUF_ASSERT_RELEASE(!key_exists);
|
|
// bool dumb_copy_key = ht->key_funcs.move == NULL;
|
|
// bool dumb_copy_val = ht->val_funcs.move == NULL;
|
|
// insert_kv(&ht_new, key_get(ht, i), val_get(ht, i), new_idx, GUF_DICT_MOVE_KEY | GUF_DICT_MOVE_VAL, dumb_copy_key, dumb_copy_val);
|
|
// ++new_size;
|
|
// }
|
|
// }
|
|
// GUF_ASSERT_RELEASE(new_size == ht->size);
|
|
// free(ht->kv_status);
|
|
// free(ht->keys);
|
|
// free(ht->vals);
|
|
// *ht = ht_new;
|
|
// }
|
|
|
|
// bool key_exists = false;
|
|
// const size_t idx = find_idx(ht, key, &key_exists, true);
|
|
// GUF_ASSERT_RELEASE(idx != SIZE_MAX);
|
|
// GUF_ASSERT_RELEASE(!kv_stat_occupied(ht->kv_status[idx]));
|
|
// if (key_exists) {
|
|
// update_v(ht, val, idx, opts);
|
|
// } else {
|
|
// insert_kv(ht, key, val, idx, opts, false, false);
|
|
// }
|
|
|
|
// return true;
|
|
// }
|
|
|
|
// bool guf_dict_contains_key(const guf_dict *ht, const void *key)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(ht);
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE (ht->keys && ht->vals && ht->kv_status);
|
|
|
|
// bool key_exists = false;
|
|
// const size_t idx = find_idx((guf_dict*)ht, key, &key_exists, false); // TODO: const cast
|
|
// GUF_ASSERT_RELEASE(!(key_exists && idx == SIZE_MAX));
|
|
// return key_exists;
|
|
// }
|
|
|
|
// void *guf_dict_get_val(guf_dict *ht, const void *key)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(ht);
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status);
|
|
// if (ht->capacity_kv_status == 0 || ht->size == 0) {
|
|
// return NULL;
|
|
// }
|
|
// bool key_exists = false;
|
|
// const size_t idx = find_idx(ht, key, &key_exists, false);
|
|
// GUF_ASSERT_RELEASE(idx != SIZE_MAX);
|
|
|
|
// if (!key_exists) {
|
|
// return NULL;
|
|
// } else {
|
|
// GUF_ASSERT(kv_stat_occupied(ht->kv_status[idx]));
|
|
// return val_get(ht, idx);
|
|
// }
|
|
// }
|
|
|
|
// bool guf_dict_remove(guf_dict *ht, const void *key)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(ht);
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status > 0 && ht->size <= ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(ht->keys && ht->vals && ht->kv_status);
|
|
|
|
// if (ht->size == 0) {
|
|
// return false;
|
|
// }
|
|
|
|
// bool key_exists = false;
|
|
// const size_t idx = find_idx(ht, key, &key_exists, false);
|
|
// if (!key_exists) {
|
|
// return false;
|
|
// }
|
|
|
|
// if (ht->key_funcs.free) {
|
|
// ht->key_funcs.free(key_get(ht, idx));
|
|
// }
|
|
// if (ht->val_funcs.free) {
|
|
// ht->val_funcs.free(val_get(ht, idx));
|
|
// }
|
|
// ht->kv_status[idx].kv_id = KV_ID_TOMBSTONE;
|
|
// --ht->size;
|
|
// ++ht->num_tombstones;
|
|
// GUF_ASSERT(ht->size + ht->num_tombstones <= ht->capacity_kv_status);
|
|
// return true;
|
|
// }
|
|
|
|
// uint32_t guf_dict_load_factor_fx10(const guf_dict *ht)
|
|
// {
|
|
// const uint64_t fx10_scale = 1024; // 2^10 (represents 1 in fx10 fixed point format).
|
|
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status >= ht->size);
|
|
|
|
// if (ht->capacity_kv_status == 0) {
|
|
// return 0;
|
|
// }
|
|
// size_t size = ht->size + ht->num_tombstones;
|
|
// // <=> size * fx_scale * fx_scale) / (ht->capacity * fx_scale)
|
|
// GUF_ASSERT(size <= ht->capacity_kv_status);
|
|
// uint64_t load_fac = (size * fx10_scale) / (uint64_t)(ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(load_fac <= fx10_scale);
|
|
// return (uint32_t)load_fac;
|
|
// }
|
|
|
|
// double guf_dict_load_factor_double(const guf_dict *ht)
|
|
// {
|
|
// GUF_ASSERT_RELEASE(ht->capacity_kv_status >= ht->size);
|
|
|
|
// if (ht->capacity_kv_status == 0 || ht->capacity_kv_status == 0) {
|
|
// return 0.f;
|
|
// }
|
|
|
|
// size_t size = ht->size + ht->num_tombstones;
|
|
// GUF_ASSERT(size <= ht->capacity_kv_status);
|
|
|
|
// return size / (double)(ht->capacity_kv_status);
|
|
// }
|
|
|
|
// void guf_dict_free(guf_dict *ht)
|
|
// {
|
|
// if (!ht) {
|
|
// return;
|
|
// }
|
|
// if (!ht->kv_status && !ht->keys && !ht->vals && ht->size == 0) {
|
|
// return;
|
|
// }
|
|
|
|
// for (size_t i = 0; i < ht->size; ++i) { // TODO: ht->size, not ht->capacity ?
|
|
// if (ht->keys && ht->key_funcs.free) {
|
|
// ht->key_funcs.free(key_get(ht, i));
|
|
// }
|
|
// if (ht->vals && ht->val_funcs.free) {
|
|
// ht->val_funcs.free(val_get(ht, i));
|
|
// }
|
|
// }
|
|
|
|
|
|
// free(ht->keys);
|
|
// free(ht->vals);
|
|
// free(ht->kv_status);
|
|
// ht->keys = NULL;
|
|
// ht->vals = NULL;
|
|
// ht->kv_status = NULL;
|
|
// ht->capacity_kv_status = ht->size = ht->num_tombstones = ht->max_probelen = 0;
|
|
// *ht = GUF_DICT_UNINITIALISED;
|
|
// }
|
|
|
|
// guf_dict_iter guf_dict_iter_begin(guf_dict *ht)
|
|
// {
|
|
// guf_dict_iter iter = {.elems_seen = ht->size + 1, .idx = 0, .ht = ht, .key = NULL, .val = NULL};
|
|
|
|
// if (ht->size == 0) {
|
|
// return iter; // end iter
|
|
// }
|
|
|
|
// for (size_t idx = 0; idx < ht->capacity_kv_status; ++idx) {
|
|
// if (kv_stat_occupied(ht->kv_status[idx])) {
|
|
// iter.idx = idx;
|
|
// iter.elems_seen = 1;
|
|
// iter.key = key_get(iter.ht, iter.idx);
|
|
// iter.val = val_get(iter.ht, iter.idx);
|
|
// GUF_ASSERT_RELEASE(iter.key != NULL);
|
|
// return iter;
|
|
// }
|
|
// }
|
|
|
|
// return iter; // end iter
|
|
// }
|
|
|
|
// bool guf_dict_iter_is_end(guf_dict_iter *iter)
|
|
// {
|
|
// return iter->elems_seen == iter->ht->size + 1;
|
|
// }
|
|
|
|
// void guf_dict_iter_advance(guf_dict_iter *iter)
|
|
// {
|
|
// if (guf_dict_iter_is_end(iter)) {
|
|
// return;
|
|
// }
|
|
|
|
// if (iter->elems_seen == iter->ht->size) {
|
|
// ++iter->elems_seen;
|
|
// iter->key = NULL;
|
|
// iter->val = NULL;
|
|
// return;
|
|
// }
|
|
|
|
// GUF_ASSERT_RELEASE(iter->elems_seen < iter->ht->size);
|
|
// GUF_ASSERT_RELEASE(iter->idx < iter->ht->capacity_kv_status);
|
|
// GUF_ASSERT_RELEASE(iter->key);
|
|
|
|
// for (size_t idx = iter->idx + 1; idx < iter->ht->capacity_kv_status; ++idx) {
|
|
// if (kv_stat_occupied(iter->ht->kv_status[idx])) {
|
|
// iter->idx = idx;
|
|
// iter->key = key_get(iter->ht, iter->idx);
|
|
// iter->val = val_get(iter->ht, iter->idx);
|
|
// ++iter->elems_seen;
|
|
// return;
|
|
// }
|
|
// }
|
|
// GUF_ASSERT_RELEASE(false);
|
|
// iter->elems_seen = iter->ht->size + 1;
|
|
// }
|
|
|
|
|
|
// /*
|
|
// Removal of keys without tombstones (only would work for linear probing I think).
|
|
|
|
// cf. https://stackoverflow.com/questions/9127207/hash-table-why-deletion-is-difficult-in-open-addressing-scheme/24886657#24886657 (last-retrieved 2024-07-26)
|
|
// The following del function from https://github.com/attractivechaos/klib/blob/6f73c80c6409d6f91cdf66ec1a002177274da2e7/cpp/khashl.hpp#L142-L150 (last-retrieved 2024-07-26)
|
|
|
|
// int del(khint_t i) {
|
|
// khint_t j = i, k, mask, nb = n_buckets();
|
|
// if (keys == 0 || i >= nb) return 0;
|
|
// mask = nb - khint_t(1);
|
|
// while (1) {
|
|
// j = (j + khint_t(1)) & mask;
|
|
// if (j == i || !__kh_used(used, j)) break; //j==i only when the table is completely full
|
|
// k = __kh_h2b(Hash()(keys[j]), bits);
|
|
// if (k <= i || k > j)
|
|
// keys[i] = keys[j], i = j;
|
|
// }
|
|
// __kh_set_unused(used, i);
|
|
// --count;
|
|
// return 1;
|
|
// }
|
|
|
|
// cf. https://en.wikipedia.org/w/index.php?title=Hash_table&oldid=95275577 (last-retrieved 2024-07-26)
|
|
// Note:
|
|
// - For all records in a cluster, there must be no vacant slots between their natural hash position
|
|
// and their current position (else lookups will terminate before finding the record).
|
|
|
|
// - i is a vacant slot that might be invalidating this property for subsequent records in the cluster.
|
|
// - j is such a subsequent record.
|
|
// - k is the raw hash where the record at j would naturally land in the hash table if there were no collisions.
|
|
|
|
// - This test is asking if the record at j is invalidly positioned with respect
|
|
// to the required properties of a cluster now that i is vacant.
|
|
// */
|