// #include // #include // #include // #include // #include // #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. // */