libguf/src/guf_dict.h
2025-03-04 06:52:42 +01:00

813 lines
33 KiB
C
Executable File

#if defined(GUF_DICT_IMPL_STATIC)
#define GUF_DICT_KWRDS static
#else
#define GUF_DICT_KWRDS
#endif
#ifndef GUF_DICT_H
#define GUF_DICT_H
#include "guf_common.h"
#include "guf_alloc.h"
#include "guf_hash.h"
typedef struct guf_dict_kv_meta_32 {
uint32_t kv_idx; // Index into the kv_elems dbuf.
uint32_t key_hash;
} guf_dict_kv_meta_32;
typedef struct guf_dict_kv_meta_64 {
uint64_t kv_idx; // Index into the kv_elems dbuf.
uint64_t key_hash;
} guf_dict_kv_meta_64;
typedef struct guf_dict_kv_meta {
guf_hash_size_t kv_idx; // Index into the kv_elems dbuf.
guf_hash_size_t key_hash;
} guf_dict_kv_meta;
#endif
#ifndef GUF_DICT_KEY_T
#error "Undefined container template GUF_DICT_KEY_T"
#endif
#ifndef GUF_DICT_KEY_HASH
#error "Undefined container template GUF_DICT_KEY_HASH"
#endif
#if !defined(GUF_DICT_KEY_T_EQ) && !defined(GUF_DICT_KEY_T_IS_INTEGRAL_TYPE)
#error "Undefined container template GUF_DICT_KEY_T_EQ"
#endif
#ifndef GUF_DICT_VAL_T
#define GUF_DICT_IS_SET
#endif
#if defined(GUF_DICT_32_BIT)
#define GUF_DICT_SIZE_T uint32_t
#define GUF_DICT_KV_META_T guf_dict_kv_meta_32
#define GUF_DICT_KV_IDX_NULL UINT32_MAX
#elif defined(GUF_DICT_64_BIT)
#define GUF_DICT_SIZE_T uint64_t
#define GUF_DICT_KV_META_T guf_dict_kv_meta_64
#define GUF_DICT_KV_IDX_NULL UINT64_MAX
#else
#define GUF_DICT_SIZE_T guf_hash_size_t
#define GUF_DICT_KV_META_T guf_dict_kv_meta
#define GUF_DICT_KV_IDX_NULL GUF_HASH_MAX
#endif
#define GUF_DICT_KV_IDX_TOMBSTONE (GUF_DICT_KV_IDX_NULL - 1)
// TODO
#define GUF_DICT_MAX_SIZE GUF_MIN(GUF_DICT_KV_IDX_TOMBSTONE - 1, PTRDIFF_MAX)
// #ifndef GUF_DICT_KEY_LOOKUP_T
// #define GUF_DICT_KEY_LOOKUP_T GUF_DICT_KEY_T
// #else
// // GUF_DICT_KEY_LOOKUP_T convert(const GUF_DICT_KEY_T *key)
// #ifndef GUF_DICT_KEY_TO_LOOKUP_KEY_CONVERT
// #error "GUF_DICT_KEY_TO_LOOKUP_KEY_CONVis must be defined since GUF_DICT_KEY_LOOKUP_T is defined"
// #endif
// #endif
#ifndef GUF_DICT_NAME
#define GUF_DICT_NAME GUF_CAT(dict_, GUF_CAT(GUF_DICT_KEY_T, GUF_CAT(_to_, GUF_DICT_VAL_T)))
#endif
#ifndef GUF_DICT_KV_NAME
#define GUF_DICT_KV_NAME GUF_CAT(GUF_DICT_NAME, _kv)
#endif
#define GUF_DICT_KV_DBUF GUF_CAT(GUF_DICT_KV_NAME, _dbuf)
// - GUF_T_COPY: cpy function with signature GUF_T *copy(GUF_T *dst, const GUF_T *src, void *ctx) (default: copy by value)
// - GUF_T_MOVE: move function with signature GUF_T *move(GUF_T *dst, GUF_T *src, void *ctx) (default: undefined)
// - GUF_T_FREE: free function with signature void free(GUF_T *a, void *ctx) (default: undefined)
// - GUF_T_EQ: equality function with signature bool eq(const GUF_T *a, const GUF_T *a) (default: undefined, or equality by value if GUF_T_IS_INTEGRAL_TYPE is defined)
#ifndef GUF_DICT_IMPL
typedef struct GUF_DICT_KV_NAME {
GUF_DICT_KEY_T key;
#ifdef GUF_DICT_VAL_T
GUF_DICT_VAL_T val;
#endif
} GUF_DICT_KV_NAME;
#define GUF_T GUF_DICT_KV_NAME
#define GUF_DBUF_NAME GUF_DICT_KV_DBUF
#define GUF_DBUF_ONLY_TYPES
#include "guf_dbuf.h"
typedef struct GUF_DICT_NAME {
GUF_DICT_KV_DBUF kv_elems; // The actual key-value elements (contiguous in memory)
GUF_DICT_KV_META_T *kv_indices; // Indices into the kv_elems dbuf.
ptrdiff_t kv_indices_cap, num_tombstones;
ptrdiff_t max_probelen; // Stores the worst-case probe-length (for performance measurement)
} GUF_DICT_NAME;
typedef GUF_CAT(GUF_DICT_KV_DBUF, _iter) GUF_CAT(GUF_DICT_NAME, _iter);
#endif
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err);
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt);
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key);
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key);
#ifdef GUF_DICT_VAL_T
GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key);
GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key);
#endif
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key);
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key);
GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht);
GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht);
/* Iterator functions */
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _begin)(const GUF_DICT_NAME* ht);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _end)(const GUF_DICT_NAME* ht);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rbegin)(const GUF_DICT_NAME* ht);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rend)(const GUF_DICT_NAME* ht);
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _iter_is_end)(const GUF_DICT_NAME* ht, GUF_CAT(GUF_DICT_NAME, _iter) it);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_next)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it, ptrdiff_t step);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _reverse_iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx);
GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _iter_to_idx)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it);
#if defined(GUF_DICT_VAL_T) && (defined(GUF_DICT_VAL_T_EQ) || defined(GUF_DICT_VAL_T_IS_INTEGRAL_TYPE))
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, const GUF_DICT_VAL_T *needle_val);
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_val_arg)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, GUF_DICT_VAL_T needle_val);
#endif
#if defined(GUF_DICT_VAL_T)
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, bool (*predicate)(const GUF_DICT_VAL_T *));
#endif
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(const GUF_DICT_NAME *ht);
// #define GUF_DICT_IMPL /* DEBUGGGGGGGGG */
#if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC)
#include "guf_assert.h"
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(const GUF_DICT_NAME *ht)
{
ptrdiff_t cnt = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) {
if (ht->kv_indices[i].kv_idx != GUF_DICT_KV_IDX_NULL && ht->kv_indices[i].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE) {
++cnt;
}
}
return cnt == ht->kv_elems.size;
}
static inline void GUF_CAT(GUF_DICT_KV_NAME, _free)(GUF_DICT_KV_NAME *kv, void *ctx)
{
(void)ctx;
#ifdef GUF_DICT_KEY_T_FREE
GUF_DICT_KEY_T_FREE(&kv->key, NULL);
#endif
#ifdef GUF_DICT_VAL_T_FREE
GUF_DICT_VAL_T_FREE(&kv->val, NULL);
#endif
#if !defined(GUF_DICT_KEY_T_FREE) && !defined(GUF_DICT_VAL_T_FREE)
(void)kv;
#endif
}
#define GUF_T GUF_DICT_KV_NAME
#define GUF_T_FREE GUF_CAT(GUF_DICT_KV_NAME, _free)
#define GUF_DBUF_NAME GUF_DICT_KV_DBUF
#define GUF_DBUF_WITHOUT_TYPES
#define GUF_DBUF_IMPL_STATIC
#include "guf_dbuf.h"
GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht)
{
if (ht->kv_indices_cap == 0) {
return 1;
}
ptrdiff_t occupied_count = ht->kv_elems.size + ht->num_tombstones;
GUF_ASSERT(occupied_count <= ht->kv_indices_cap);
return (double)occupied_count / (double)ht->kv_indices_cap;
}
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err)
{
if (!ht || !alloc) {
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in dict_try_init: ht or alloc NULL"));
return NULL;
}
ht->kv_elems = (GUF_DICT_KV_DBUF){0};
GUF_CAT(GUF_DICT_KV_DBUF, _try_init)(&ht->kv_elems, 0, alloc, err);
if (err != GUF_ERR_NONE) {
return NULL;
}
ht->kv_indices = NULL;
ht->kv_indices_cap = 0;
ht->num_tombstones = 0;
ht->max_probelen = 0;
return ht;
}
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc)
{
return GUF_CAT(GUF_DICT_NAME, _try_init)(ht, alloc, NULL);
}
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht)
{
if (!ht) {
return false;
}
bool kv_dbuf_valid = GUF_CAT(GUF_DICT_KV_DBUF, _valid)(&ht->kv_elems);
bool kv_meta_buf_valid = (!ht->kv_indices && !ht->kv_indices_cap) || (ht->kv_indices && ht->kv_indices_cap);
bool count_valid = ht->num_tombstones >= 0 && ht->kv_elems.size >= 0 && ((ht->kv_elems.size + ht->num_tombstones) <= ht->kv_indices_cap);
return kv_dbuf_valid && kv_meta_buf_valid && count_valid;
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx)
{
(void)ctx;
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
guf_allocator *allocator = ht->kv_elems.allocator;
if (ht->kv_indices) {
allocator->free(ht->kv_indices, ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T), allocator->ctx);
ht->kv_indices = NULL;
ht->kv_indices_cap = 0;
}
GUF_CAT(GUF_DICT_KV_DBUF, _free)(&ht->kv_elems, NULL);
ht->num_tombstones = 0;
ht->max_probelen = 0;
}
GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
return ht->kv_elems.size;
}
static inline size_t GUF_CAT(GUF_DICT_NAME, _probe_offset)(size_t probe_len)
{
GUF_ASSERT(probe_len > 0);
#ifdef GUF_DICT_PROBE_LINEAR
return probe_len; // 1, 2, 3, 4, 5, ...
#else
/*
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, 21 ... (starting from probe_len == 1)
#endif
}
static size_t GUF_CAT(GUF_DICT_NAME, _find_idx)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key, bool *key_exists)
{
if (ht->kv_indices_cap <= 0) {
*key_exists = false;
return SIZE_MAX;
}
const GUF_DICT_SIZE_T hash = GUF_DICT_KEY_HASH(key);
#define GUF_MOD_CAP(a) ((size_t)(a) & (size_t)(ht->kv_indices_cap - 1)) // a % ht->kv_indices_cap (kv_indices_cap must be a power of two > 0)
size_t idx = GUF_MOD_CAP(hash);
const size_t start_idx = idx;
size_t first_tombstone_idx = SIZE_MAX;
size_t probe_len = 0;
// size_t seen_occupied = 0; // This allows us to bail out early once we visited every non-null/non-tombstone kv_idx.
do {
if (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_NULL) { // 1.) Empty.
if (first_tombstone_idx != SIZE_MAX) {
idx = first_tombstone_idx;
}
ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen);
GUF_ASSERT((ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_NULL) || (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE));
*key_exists = false;
return idx;
} else if (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { // 2.) Tombstone.
if (first_tombstone_idx == SIZE_MAX) {
first_tombstone_idx = idx;
}
goto probe;
} else if (hash == ht->kv_indices[idx].key_hash && GUF_DICT_KEY_T_EQ(key, &(GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, ht->kv_indices[idx].kv_idx)->key))) { // 3.) Key already exists.
ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen);
*key_exists = true;
return idx;
} else { // 4.) Have to probe due to hash-collision/tombstone.
probe:
++probe_len;
// if (ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_NULL && ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE) {
// ++seen_occupied; // && seen_occupied <= (size_t)ht->kv_elems.size
// }
idx = GUF_MOD_CAP(start_idx + GUF_CAT(GUF_DICT_NAME, _probe_offset)(probe_len)); // NOTE: Add probe_offset to start_idx and not to idx.
GUF_ASSERT((ptrdiff_t)probe_len <= (ht->kv_elems.size + ht->num_tombstones));
}
} while (idx != start_idx && probe_len < (size_t)ht->kv_indices_cap);
*key_exists = false;
if (first_tombstone_idx != SIZE_MAX) { // Edge case: No empty slots, but found tombstone.
ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen);
GUF_ASSERT(ht->kv_indices[first_tombstone_idx].kv_idx == GUF_DICT_KV_IDX_NULL);
return first_tombstone_idx;
} else { // Failed to find an idx.
return SIZE_MAX;
}
#undef GUF_MOD_CAP
}
static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(GUF_DICT_NAME *ht, guf_err *err)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
#ifdef GUF_DICT_PROBE_LINEAR
const double MAX_LOAD_FAC = 0.6;
#else
const double MAX_LOAD_FAC = 0.5;
#endif
const ptrdiff_t KV_META_START_CAP = 32; // Must be a power of two > 0.
const ptrdiff_t KV_META_GROWTH_FAC = 2; // Must be a power of two > 1.
guf_allocator *allocator = ht->kv_elems.allocator;
if (ht->kv_indices_cap == 0) { // 1.a) Allocate initial kv-index-buffer.
GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(KV_META_START_CAP * sizeof(GUF_DICT_KV_META_T), allocator->ctx);
if (new_kv_indices == NULL) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_grow: Initial allocation failed"));
return;
}
ht->kv_indices = new_kv_indices;
ht->kv_indices_cap = KV_META_START_CAP;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) {
new_kv_indices[i].kv_idx = GUF_DICT_KV_IDX_NULL;
new_kv_indices[i].key_hash = 0;
}
} else if (GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) > MAX_LOAD_FAC) { // 1.b) Grow kv-index-buffer if necessary.
GUF_ASSERT(ht->kv_indices);
const ptrdiff_t old_size = ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T);
ptrdiff_t new_size = 0;
if (!guf_size_calc_safe(old_size, KV_META_GROWTH_FAC, &new_size)) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: New capacity would overflow)"));
return;
}
// TODO: Not sure if alloc and free is better here than realloc (since we do not copy ht->kv_indices anyway.)
GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(new_size, allocator->ctx);
if (new_kv_indices == NULL) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: re-allocation failed"));
return;
}
allocator->free(ht->kv_indices, old_size, allocator->ctx);
ht->kv_indices = new_kv_indices;
ht->kv_indices_cap = ht->kv_indices_cap * KV_META_GROWTH_FAC;
ht->num_tombstones = 0;
// ht->max_probelen = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) {
ht->kv_indices[i].kv_idx = GUF_DICT_KV_IDX_NULL;
ht->kv_indices[i].key_hash = 0;
}
for (ptrdiff_t kv_idx = 0; kv_idx < ht->kv_elems.size; ++kv_idx) { // Re-insert keys.
const GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx);
GUF_ASSERT(kv);
bool key_exists = false;
const size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, &kv->key, &key_exists);
GUF_ASSERT(!key_exists);
GUF_ASSERT(new_idx < SIZE_MAX);
ht->kv_indices[new_idx].kv_idx = kv_idx;
ht->kv_indices[new_idx].key_hash = GUF_DICT_KEY_HASH(&kv->key);
}
}
guf_err_set_if_not_null(err, GUF_ERR_NONE);
GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) <= MAX_LOAD_FAC);
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
if (!key || !val) {
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function dict_try_insert: key or val argument is NULL"));
return;
}
if ((size_t)ht->kv_elems.size == GUF_DICT_MAX_SIZE) {
guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function dict_try_insert: dict has reached its max size (UINT64_MAX - 2 or UINT32_MAX - 2)"));
return;
}
// 1.) Grow kv-index-buffer if neccessary (or make the initial allocation.)
GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(ht, err);
if (err != NULL && *err != GUF_ERR_NONE) {
guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in function dict_try_insert: try_grow failed."));
return;
}
GUF_ASSERT_RELEASE(ht->kv_indices_cap > ht->kv_elems.size);
// 2.) Insert new key-value pair.
bool key_exists = false;
size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (key_exists) {
guf_err_set_or_panic(err, GUF_ERR_ALREADY_EXISTS, GUF_ERR_MSG("in function dict_try_insert: Key already exists"));
return;
}
GUF_ASSERT_RELEASE(idx < (size_t)ht->kv_indices_cap);
if (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) {
ht->num_tombstones -= 1;
GUF_ASSERT_RELEASE(ht->num_tombstones >= 0);
}
ht->kv_indices[idx].key_hash = GUF_DICT_KEY_HASH(key);
ht->kv_indices[idx].kv_idx = ht->kv_elems.size;
GUF_DICT_KEY_T key_cpy;
GUF_DICT_KEY_T *key_cpy_res = NULL;
if (key_opt == GUF_CPY_DEEP) {
#ifdef GUF_DICT_KEY_T_COPY
key_cpy_res = GUF_DICT_KEY_T_COPY(&key_cpy, key);
#else
GUF_ASSERT_RELEASE(false);
#endif
} else if (key_opt == GUF_CPY_MOVE) {
#ifdef GUF_DICT_KEY_T_MOVE
key_cpy_res = GUF_DICT_KEY_T_MOVE(&key_cpy, key);
#else
GUF_ASSERT_RELEASE(false);
#endif
} else {
key_cpy = *key;
key_cpy_res = &key_cpy;
}
if (!key_cpy_res) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Failed to copy key"));
return;
}
GUF_DICT_VAL_T val_cpy;
GUF_DICT_VAL_T *val_cpy_res = NULL;
if (val_opt == GUF_CPY_DEEP) {
#ifdef GUF_DICT_VAL_T_COPY
val_cpy_res = GUF_DICT_KEY_T_COPY(&val_cpy, val);
#else
GUF_ASSERT_RELEASE(false);
#endif
} else if (val_opt == GUF_CPY_MOVE) {
#ifdef GUF_DICT_VAL_T_MOVE
val_cpy_res = GUF_DICT_KEY_T_MOVE(&val_cpy, val);
#else
GUF_ASSERT_RELEASE(false);
#endif
} else {
val_cpy = *val;
val_cpy_res = &val_cpy;
}
if (!val_cpy_res) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Failed to copy value"));
if (key_opt == GUF_CPY_DEEP || key_opt == GUF_CPY_MOVE) {
#ifdef GUF_DICT_KEY_T_FREE
GUF_DICT_KEY_T_FREE(key_cpy_res, NULL);
#endif
}
return;
}
GUF_DICT_KV_NAME kv = {.key = key_cpy, .val = val_cpy};
GUF_CAT(GUF_DICT_KV_DBUF, _try_push_val)(&ht->kv_elems, kv, err);
if (err && *err != GUF_ERR_NONE) { // Insertion failed.
GUF_ASSERT(*err != GUF_ERR_IDX_RANGE && *err != GUF_ERR_INVALID_ARG);
#ifdef GUF_DICT_KEY_T_FREE
GUF_DICT_KEY_T_FREE(&kv.key, NULL);
#endif
#ifdef GUF_DICT_VAL_T_FREE
GUF_DICT_VAL_T_FREE(&kv.val, NULL);
#endif
}
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt)
{
GUF_CAT(GUF_DICT_NAME, _try_insert)(ht, key, val, key_opt, val_opt, NULL);
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err)
{
GUF_CAT(GUF_DICT_NAME, _try_insert)(ht, &key, &val, key_opt, val_opt, err);
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt)
{
GUF_CAT(GUF_DICT_NAME, _insert)(ht, &key, &val, key_opt, val_opt);
}
#ifdef GUF_DICT_VAL_T
GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
if (!key) {
return NULL;
}
bool key_exists = false;
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (!key_exists) {
return NULL;
} else {
GUF_ASSERT(idx != SIZE_MAX);
GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap);
const size_t kv_idx = ht->kv_indices[idx].kv_idx;
GUF_ASSERT((ptrdiff_t)kv_idx < ht->kv_elems.size);
return &GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx)->val;
}
}
GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key)
{
return GUF_CAT(GUF_DICT_NAME, _at)(ht, &key);
}
#endif
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
if (!key) {
return false;
}
bool key_exists = false;
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (key_exists) {
GUF_ASSERT(idx != SIZE_MAX);
GUF_ASSERT(ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE);
GUF_ASSERT(ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_NULL);
}
(void)idx;
return key_exists;
}
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key)
{
return GUF_CAT(GUF_DICT_NAME, _contains)(ht, &key);
}
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
if (!key || ht->kv_elems.size == 0) {
return false;
}
bool key_exists = false;
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (!key_exists) {
return false;
}
GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap);
const size_t kv_idx = ht->kv_indices[idx].kv_idx;
GUF_ASSERT(kv_idx < (size_t)ht->kv_elems.size);
ht->kv_indices[idx].kv_idx = GUF_DICT_KV_IDX_TOMBSTONE;
ht->kv_indices[idx].key_hash = 0;
ht->num_tombstones += 1;
GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx);
GUF_ASSERT(kv);
GUF_CAT(GUF_DICT_KV_NAME, _free)(kv, NULL);
if (ht->kv_elems.size > 1 && (ptrdiff_t)kv_idx != ht->kv_elems.size - 1) { // Switch last kv-elem into the erased position and update its kv-index accordingly.
// 1.) Switch kv_elem.
GUF_DICT_KV_NAME *last_kv = GUF_CAT(GUF_DICT_KV_DBUF, _back)(&ht->kv_elems);
GUF_ASSERT(last_kv);
GUF_ASSERT(kv != last_kv);
*kv = *last_kv;
// GUF_ASSERT(!GUF_DICT_KEY_T_EQ(key, &last_kv->key));
// 2.) Update kv_index.
bool last_key_exists = false;
const size_t last_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, &last_kv->key, &last_key_exists);
GUF_ASSERT(last_idx != idx);
GUF_ASSERT(last_key_exists && (ptrdiff_t)last_idx < ht->kv_indices_cap);
GUF_ASSERT((ptrdiff_t)ht->kv_indices[last_idx].kv_idx == ht->kv_elems.size - 1);
GUF_ASSERT(ht->kv_indices[last_idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE && ht->kv_indices[last_idx].kv_idx != GUF_DICT_KV_IDX_NULL);
ht->kv_indices[last_idx].kv_idx = kv_idx;
}
ht->kv_elems.size -= 1;
GUF_ASSERT(ht->kv_elems.size >= 0);
GUF_ASSERT(ht->num_tombstones <= ht->kv_indices_cap);
// GUF_ASSERT(!GUF_CAT(GUF_DICT_NAME, _contains)(ht, key));
if (ht->kv_elems.size == 0 && ht->num_tombstones > 0) { // Optimisation: We can delete all tombstones here.
ptrdiff_t del_tombstone_cnt = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap && del_tombstone_cnt < ht->num_tombstones; ++i) {
GUF_ASSERT(ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE || ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_NULL);
if (ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) {
ht->kv_indices[i].kv_idx = GUF_DICT_KV_IDX_NULL;
ht->kv_indices[i].key_hash = 0;
++del_tombstone_cnt;
} else {
GUF_ASSERT(ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_NULL);
}
}
GUF_ASSERT(del_tombstone_cnt == ht->num_tombstones);
ht->num_tombstones = 0;
}
return true;
}
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key)
{
return GUF_CAT(GUF_DICT_NAME, _erase)(ht, &key);
}
/* Iterator functions */
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _begin)(const GUF_DICT_NAME* ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _begin)(&ht->kv_elems);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _end)(const GUF_DICT_NAME* ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _end)(&ht->kv_elems);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rbegin)(const GUF_DICT_NAME* ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _rbegin)(&ht->kv_elems);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rend)(const GUF_DICT_NAME* ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _rend)(&ht->kv_elems);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _iter_is_end)(const GUF_DICT_NAME* ht, GUF_CAT(GUF_DICT_NAME, _iter) it)
{
const bool is_reverse_it = it.base != NULL;
const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht);
return it.ptr == dbuf_end_it.ptr;
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_next)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it, ptrdiff_t step)
{
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _iter_next)(&ht->kv_elems, it, step);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx)
{
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _iter_at_idx)(&ht->kv_elems, idx);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _reverse_iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx)
{
GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _reverse_iter_at_idx)(&ht->kv_elems, idx);
return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base};
}
GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _iter_to_idx)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it)
{
return GUF_CAT(GUF_DICT_KV_DBUF, _iter_to_idx)(&ht->kv_elems, it);
}
#if defined(GUF_DICT_VAL_T) && (defined(GUF_DICT_VAL_T_EQ) || defined(GUF_DICT_VAL_T_IS_INTEGRAL_TYPE))
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, const GUF_DICT_VAL_T *needle)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_ASSERT_RELEASE(needle);
const bool is_reverse_it = begin.base != NULL;
GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type.
const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht);
if (!ht->kv_elems.data || !ht->kv_elems.size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) {
return dbuf_end_it;
}
if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) {
return dbuf_end_it;
}
for (GUF_CAT(GUF_DICT_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DICT_NAME, _iter_next)(ht, it, 1)) {
#ifdef GUF_DICT_VAL_T_EQ
if (GUF_DICT_VAL_T_EQ(&(it.ptr->val), needle)) {
return it;
}
#else
if (it.ptr->val == *needle) {
return it;
}
#endif
}
return dbuf_end_it;
}
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_val_arg)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, GUF_DICT_VAL_T needle)
{
return GUF_CAT(GUF_DICT_NAME, _find_val)(ht, begin, end, &needle);
}
#endif
#if defined(GUF_DICT_VAL_T)
GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, bool (*predicate)(const GUF_DICT_VAL_T *))
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_ASSERT_RELEASE(predicate);
const bool is_reverse_it = begin.base != NULL;
GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type.
const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht);
if (!ht->kv_elems.data || !ht->kv_elems.size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) {
return dbuf_end_it;
}
if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) {
return dbuf_end_it;
}
for (GUF_CAT(GUF_DICT_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DICT_NAME, _iter_next)(ht, it, 1)) {
if (predicate(&(it.ptr->val))) {
return it;
}
}
return dbuf_end_it;
}
#endif
#endif /* end GUF_IMPL/GUF_IMPL_STATIC */
#undef GUF_DICT_KV_IDX_NULL
#undef GUF_DICT_KV_IDX_TOMBSTONE
#undef GUF_DICT_32_BIT
#undef GUF_DICT_SIZE_T
#undef GUF_DICT_MAX_SIZE
#undef GUF_DICT_KV_META_T
#undef GUF_DICT_NAME
#undef GUF_DICT_IS_SET
#undef GUF_DICT_PROBE_LINEAR
#undef GUF_DICT_PROBE_QUADRATIC
#undef GUF_DICT_KEY_T
#undef GUF_DICT_KEY_T_IS_INTEGRAL_TYPE
#undef GUF_DICT_KEY_T_EQ
#undef GUF_DICT_KEY_T_FREE
#undef GUF_DICT_KEY_T_CMP
#undef GUF_DICT_KEY_T_COPY
#undef GUF_DICT_KEY_T_MOVE
#undef GUF_DICT_KEY_HASH
#undef GUF_DICT_VAL_T
#undef GUF_DICT_VAL_T_IS_INTEGRAL_TYPE
#undef GUF_DICT_VAL_T_EQ
#undef GUF_DICT_VAL_T_FREE
#undef GUF_DICT_VAL_T_CMP
#undef GUF_DICT_VAL_T_COPY
#undef GUF_DICT_VAL_T_MOVE
#undef GUF_DICT_KEY_LOOKUP_T
#undef GUF_DICT_KEY_TO_LOOKUP_KEY_CONVERT
#undef GUF_DICT_KV_NAME
#undef GUF_DICT_KV_DBUF
#undef GUF_DICT_IMPL_STATIC
#undef GUF_DICT_IMPL
#undef GUF_DICT_KWRDS