libguf/src/guf_dict.h

1053 lines
46 KiB
C
Executable File

/*
is parametrized: yes
*/
#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"
// MAX_LOAD_FACTOR must be in range [0.1, 0.9]
#define GUF_DICT_MAX_LOAD_FACTOR_DEFAULT 0.666
#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_HASH)
#define GUF_DICT_HASH_T uint32_t
#define GUF_DICT_HASH_T_MAX UINT32_MAX
#elif defined(GUF_DICT_64_BIT_HASH)
#define GUF_DICT_HASH_T uint64_t
#define GUF_DICT_HASH_T_MAX UINT64_MAX
#else
#define GUF_DICT_HASH_T guf_hash_size_t
#define GUF_DICT_HASH_T_MAX GUF_HASH_MAX
#endif
#if defined (GUF_DICT_64_BIT_IDX)
#define GUF_DICT_KV_META_T uint64_t
/*
Store a 16-bit hash-fragment in the upper 16-bits of kv_meta
-> (2^48 - 1 is IDX_NULL, 2^48 - 2 is IDX_TOMBSTONE, 2^48 - 3 is the largest actual idx,
i.e. the max amount of actual kv_elems the dict could hold is 2^48 - 2 = 281,474,976,710,654).
*/
#define GUF_DICT_KV_META_HASHFRAG_MASK UINT64_C(0xffff000000000000)
#define GUF_DICT_KV_META_IDX_MASK (~UINT64_C(0xffff000000000000))
#if GUF_DICT_HASH_T_MAX == UINT64_MAX
#define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (HASH) & GUF_DICT_KV_META_HASHFRAG_MASK )
#elif GUF_DICT_HASH_T_MAX == UINT32_MAX
#define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (((uint64_t)(HASH)) << 32) & GUF_DICT_KV_META_HASHFRAG_MASK )
#else
#error "guf_dict: invalid hash size (should not happen)"
#endif
#else
#define GUF_DICT_KV_META_T uint32_t
/*
Store a 7-bit hash-fragment in the upper 7-bits of kv_meta
-> (2^25 - 1 is IDX_NULL, 2^25 - 2 is IDX_TOMBSTONE, 2^25 - 3 is the largest actual idx,
i.e. the max amount of actual kv_elems the dict could hold is 2^25 - 2 = 33,554,430).
*/
#define GUF_DICT_KV_META_HASHFRAG_MASK UINT32_C(0xfe000000)
#define GUF_DICT_KV_META_IDX_MASK (~UINT32_C(0xfe000000))
#if GUF_DICT_HASH_T_MAX == UINT64_MAX
#define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( ((uint32_t)((HASH) >> 32)) & GUF_DICT_KV_META_HASHFRAG_MASK )
#elif GUF_DICT_HASH_T_MAX == UINT32_MAX
#define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (HASH) & GUF_DICT_KV_META_HASHFRAG_MASK )
#else
#error "guf_dict: invalid hash size (should not happen)"
#endif
#endif
#define GUF_DICT_KV_META_IDX_NULL GUF_DICT_KV_META_IDX_MASK
#define GUF_DICT_KV_META_IDX_TOMBSTONE (GUF_DICT_KV_META_IDX_NULL - 1)
#define GUF_DICT_KV_META_IDX_MAX (GUF_DICT_KV_META_IDX_TOMBSTONE - 1)
#define GUF_DICT_META_GET_IDX(META) ( (META) & GUF_DICT_KV_META_IDX_MASK )
#define GUF_DICT_META_GET_HASHFRAG(META) ( (META) & GUF_DICT_KV_META_HASHFRAG_MASK )
#define GUF_DICT_META_IS_NULL(META) ( GUF_DICT_META_GET_IDX(META) == GUF_DICT_KV_META_IDX_NULL )
#define GUF_DICT_META_IS_TOMBSTONE(META) ( GUF_DICT_META_GET_IDX(META) == GUF_DICT_KV_META_IDX_TOMBSTONE )
#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
#ifndef GUF_DICT_MAX_LOAD_FACTOR
#define GUF_DICT_MAX_LOAD_FACTOR GUF_DICT_MAX_LOAD_FACTOR_DEFAULT
#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_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity, guf_err *err);
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity);
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 GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _copy)(GUF_DICT_NAME *dst, const GUF_DICT_NAME *src, void *ctx);
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _move)(GUF_DICT_NAME *dst, GUF_DICT_NAME *src, 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);
GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor_without_tombstones)(const GUF_DICT_NAME *ht);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_rehash_and_grow)(GUF_DICT_NAME *ht, guf_err *err);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _rehash_without_growth)(GUF_DICT_NAME *ht);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_shrink_to_fit)(GUF_DICT_NAME *ht, guf_err *err);
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _shrink_to_fit)(GUF_DICT_NAME *ht);
GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _max_capacity)(void);
GUF_DICT_KWRDS size_t GUF_CAT(GUF_DICT_NAME, _memory_usage)(const GUF_DICT_NAME *ht);
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht);
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(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
// #define GUF_DICT_IMPL /* DEBUGGGGGGGGG */
#if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC)
#include <string.h>
#include "guf_assert.h"
#include "guf_math.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 (!GUF_DICT_META_IS_NULL(ht->kv_indices[i]) && !GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[i])) {
++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 double GUF_CAT(GUF_DICT_NAME, _load_factor_without_tombstones)(const GUF_DICT_NAME *ht)
{
if (ht->kv_indices_cap == 0) {
return 1;
}
GUF_ASSERT(ht->kv_elems.size <= ht->kv_indices_cap);
return (double)ht->kv_elems.size / (double)ht->kv_indices_cap;
}
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity, guf_err *err)
{
GUF_ASSERT(GUF_DICT_MAX_LOAD_FACTOR >= 0.1 && GUF_DICT_MAX_LOAD_FACTOR <= 0.9);
if (!ht || !alloc) {
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in dict_try_init_with_capacity: ht or alloc NULL"));
return NULL;
} else if (kv_elem_capacity < 0 || kv_elem_capacity > GUF_CAT(GUF_DICT_NAME, _max_capacity)()) {
guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in dict_try_init_with_capacity: kv_elem_capacity < 0 or kv_elem_capacity > max_capacity"));
return NULL;
}
ht->kv_indices = NULL;
ht->kv_indices_cap = 0;
ht->num_tombstones = ht->max_probelen = 0;
ht->kv_elems = (GUF_DICT_KV_DBUF){0};
GUF_CAT(GUF_DICT_KV_DBUF, _try_init)(&ht->kv_elems, kv_elem_capacity, alloc, err);
if (err != GUF_ERR_NONE) {
return NULL;
}
if (kv_elem_capacity > 0) {
const size_t MAX_IDX_CAP = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T);
const size_t desired_idx_cap = (size_t)guf_min_f64(kv_elem_capacity * 1.0 / GUF_DICT_MAX_LOAD_FACTOR, MAX_IDX_CAP);
// Capacities must be powers of two.
size_t kv_idx_cap = 1;
while ((kv_idx_cap <= MAX_IDX_CAP / 2) && (kv_idx_cap <= desired_idx_cap)) {
kv_idx_cap <<= 1;
}
GUF_ASSERT_RELEASE(guf_is_pow2_size_t(kv_idx_cap));
GUF_ASSERT_RELEASE(kv_idx_cap >= (size_t)ht->kv_elems.capacity && kv_idx_cap <= MAX_IDX_CAP);
const size_t num_bytes = kv_idx_cap * sizeof(GUF_DICT_KV_META_T);
GUF_ASSERT_RELEASE(!guf_mul_is_overflow_size_t(kv_idx_cap, sizeof(GUF_DICT_KV_META_T)) && num_bytes <= GUF_ALLOC_MAX_BYTES(GUF_DICT_KV_META_T));
GUF_DICT_KV_META_T *kv_indices = ht->kv_elems.allocator->alloc(num_bytes, ht->kv_elems.allocator->ctx);
if (!kv_indices) {
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in dict_try_init_with_capacity: allocation of ht->kv_indices failed"));
GUF_CAT(GUF_DICT_KV_DBUF, _free)(&ht->kv_elems, NULL);
return NULL;
}
ht->kv_indices = kv_indices;
ht->kv_indices_cap = kv_idx_cap;
GUF_ASSERT(ht->kv_indices_cap >= ht->kv_elems.capacity);
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) {
ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL;
}
}
return ht;
}
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init_with_capacity)(GUF_DICT_NAME *ht, guf_allocator *alloc, ptrdiff_t kv_elem_capacity)
{
return GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(ht, alloc, kv_elem_capacity, NULL);
}
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err)
{
return GUF_CAT(GUF_DICT_NAME, _try_init_with_capacity)(ht, alloc, 0, err);
}
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 && guf_is_pow2_size_t(ht->kv_indices_cap));
bool count_valid = ht->num_tombstones >= 0 && ht->kv_elems.size >= 0 && (size_t)ht->kv_elems.size <= GUF_DICT_KV_META_IDX_MAX && ((ht->kv_elems.size + ht->num_tombstones) <= ht->kv_indices_cap);
return kv_dbuf_valid && kv_meta_buf_valid && count_valid;
}
GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _max_capacity)(void)
{
const size_t max_cap_kv_elems = GUF_MIN(GUF_DICT_KV_META_IDX_MAX + 1, GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_NAME));
const size_t max_cap_kv_indices = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T);
// Find next power of two (capacities must be powers of two).
size_t pow2_cap = 1;
const size_t MAX_SIZE_POW2 = SIZE_MAX & ~(SIZE_MAX >> 1);
while ( (pow2_cap < MAX_SIZE_POW2) && ((pow2_cap << 1) <= max_cap_kv_indices) ) {
pow2_cap <<= 1;
}
GUF_ASSERT(guf_is_pow2_size_t(pow2_cap) && pow2_cap <= max_cap_kv_indices && pow2_cap > 1);
return GUF_MIN(GUF_MIN(max_cap_kv_elems, pow2_cap), PTRDIFF_MAX);
}
GUF_DICT_KWRDS size_t GUF_CAT(GUF_DICT_NAME, _memory_usage)(const GUF_DICT_NAME *ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
const size_t mem_kv_indices = (size_t)ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T);
const size_t mem_kv_elems = (size_t)ht->kv_elems.capacity * sizeof(GUF_DICT_KV_NAME);
return mem_kv_indices + mem_kv_elems;
}
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 GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _copy)(GUF_DICT_NAME *dst, const GUF_DICT_NAME *src, void *ctx)
{
(void)ctx;
GUF_ASSERT_RELEASE(dst);
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(src));
GUF_ASSERT_RELEASE(dst != src);
dst->kv_indices = NULL;
dst->kv_indices_cap = dst->max_probelen = dst->num_tombstones = 0;
dst->kv_elems.allocator = NULL;
dst->kv_elems.data = NULL;
dst->kv_elems.capacity = dst->kv_elems.size = 0;
GUF_DICT_KV_DBUF *kv_elems_cpy = GUF_CAT(GUF_DICT_KV_DBUF, _copy)(&dst->kv_elems, &src->kv_elems, NULL);
if (!kv_elems_cpy) {
return NULL;
}
if (src->kv_indices) {
GUF_ASSERT(src->kv_indices_cap > 0);
const ptrdiff_t num_bytes = src->kv_indices_cap * sizeof(src->kv_indices[0]);
dst->kv_indices = src->kv_elems.allocator->alloc(num_bytes, src->kv_elems.allocator->ctx);
if (!dst->kv_indices) {
GUF_CAT(GUF_DICT_KV_DBUF, _free)(&dst->kv_elems, NULL);
return NULL;
}
memcpy(dst->kv_indices, src->kv_indices, num_bytes);
dst->kv_indices_cap = src->kv_indices_cap;
} else {
dst->kv_indices = NULL;
}
dst->max_probelen = src->max_probelen;
dst->num_tombstones = src->num_tombstones;
GUF_ASSERT(dst->kv_elems.size == src->kv_elems.size && dst->kv_elems.capacity == src->kv_elems.capacity && dst->kv_elems.allocator == src->kv_elems.allocator);
GUF_ASSERT(dst->kv_indices_cap == src->kv_indices_cap);
return dst;
}
GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _move)(GUF_DICT_NAME *dst, GUF_DICT_NAME *src, void *ctx)
{
(void)ctx;
GUF_ASSERT_RELEASE(dst);
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(src));
GUF_ASSERT_RELEASE(dst != src);
dst->kv_elems = src->kv_elems;
dst->kv_indices = src->kv_indices;
dst->kv_indices_cap = src->kv_indices_cap;
dst->max_probelen = src->max_probelen;
dst->num_tombstones = src->num_tombstones;
src->kv_indices = NULL;
src->kv_indices_cap = src->max_probelen = src->num_tombstones = 0;
src->kv_elems.allocator = NULL;
src->kv_elems.data = NULL;
src->kv_elems.capacity = src->kv_elems.size = 0;
return dst;
}
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, GUF_DICT_HASH_T key_hash, bool *key_exists)
{
if (ht->kv_indices_cap <= 0) {
*key_exists = false;
return SIZE_MAX;
}
const GUF_DICT_KV_META_T key_hash_frag = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash);
#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(key_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 {
const GUF_DICT_KV_META_T kv_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[idx]);
const GUF_DICT_KV_META_T kv_hashfrag = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[idx]);
if (kv_idx == GUF_DICT_KV_META_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);
*key_exists = false;
return idx;
} else if (kv_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) { // 2.) Tombstone.
if (first_tombstone_idx == SIZE_MAX) {
first_tombstone_idx = idx;
}
goto probe;
} else if (key_hash_frag == kv_hashfrag && GUF_DICT_KEY_T_EQ(key, &(GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, 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.) Probe because kv_idx was a tombstone or because key != kv_elems[kv_idx]
probe:
++probe_len;
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(GUF_DICT_META_GET_IDX(ht->kv_indices[first_tombstone_idx]) == GUF_DICT_KV_META_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, _reinsert_elems_)(GUF_DICT_NAME *ht)
{
GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_ASSERT_RELEASE(ht->kv_indices && ht->kv_indices_cap > 0);
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) {
ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL;
}
ht->num_tombstones = 0;
GUF_ASSERT((size_t)ht->kv_elems.size < GUF_DICT_KV_META_IDX_MAX);
for (ptrdiff_t kv_idx = 0; kv_idx < ht->kv_elems.size; ++kv_idx) {
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 GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(&kv->key);
const size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, &kv->key, key_hash, &key_exists);
GUF_ASSERT(!key_exists);
GUF_ASSERT(new_idx < SIZE_MAX && new_idx < (size_t)ht->kv_indices_cap);
GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)kv_idx) == 0);
ht->kv_indices[new_idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)kv_idx;
}
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _rehash_without_growth)(GUF_DICT_NAME *ht)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(ht);
GUF_ASSERT(ht->num_tombstones == 0);
}
static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary_)(GUF_DICT_NAME *ht, bool always_grow, guf_err *err)
{
GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
const double MAX_LOAD_FAC = GUF_DICT_MAX_LOAD_FACTOR;
GUF_ASSERT(GUF_DICT_MAX_LOAD_FACTOR >= 0.1 && GUF_DICT_MAX_LOAD_FACTOR <= 0.9);
const ptrdiff_t KV_META_START_CAP = 32; // Must be a power of two > 0.
const ptrdiff_t KV_META_GROWTH_FAC = (ht->kv_indices_cap <= 128) ? 4 : 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] = GUF_DICT_KV_META_IDX_NULL;
}
} else if ((GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) >= MAX_LOAD_FAC) || always_grow) { // 1.b) Grow kv-index-buffer if necessary.
GUF_ASSERT(ht->kv_indices);
GUF_ASSERT((size_t)ht->kv_indices_cap <= GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T));
const ptrdiff_t old_size_bytes = (size_t)ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T);
ptrdiff_t new_size_bytes = 0;
const size_t MAX_SIZE_BYTES = (size_t)GUF_ALLOC_MAX_BYTES(GUF_DICT_KV_META_T);
const size_t new_size_bytes_test = (size_t)old_size_bytes * (size_t)KV_META_GROWTH_FAC;
if (guf_mul_is_overflow_size_t(old_size_bytes, KV_META_GROWTH_FAC) || new_size_bytes_test > MAX_SIZE_BYTES) { // Handle overflow (Remember: capacities have to be powers of two)
if (GUF_CAT(GUF_DICT_NAME, _load_factor_without_tombstones)(ht) < MAX_LOAD_FAC) { // Check if just removing tombstones without resizing would decrease the load factor enough.
GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(ht);
GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) < MAX_LOAD_FAC);
guf_err_set_if_not_null(err, GUF_ERR_NONE);
return;
}
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: New kv_indices_capacity would overflow)"));
return;
} else {
GUF_ASSERT(new_size_bytes_test <= PTRDIFF_MAX);
new_size_bytes = (ptrdiff_t)new_size_bytes_test;
}
GUF_ASSERT_RELEASE(new_size_bytes > old_size_bytes);
// 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_bytes, 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;
}
// NOTE: would be more memory-efficient to free first, but that would leave the dict in an invalid state if the alloc fails.
allocator->free(ht->kv_indices, old_size_bytes, allocator->ctx);
ht->kv_indices = new_kv_indices;
ht->kv_indices_cap = ht->kv_indices_cap * KV_META_GROWTH_FAC;;
GUF_ASSERT(guf_is_pow2_size_t(ht->kv_indices_cap));
GUF_ASSERT(new_size_bytes / sizeof(GUF_DICT_KV_META_T) == ht->kv_indices_cap);
// ht->max_probelen = 0;
GUF_CAT(GUF_DICT_NAME, _reinsert_elems_)(ht);
GUF_ASSERT(ht->num_tombstones == 0);
}
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_rehash_and_grow)(GUF_DICT_NAME *ht, guf_err *err)
{
GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary_)(ht, true, err);
}
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_KV_META_IDX_MAX + 1)) {
GUF_ASSERT(ht->kv_elems.size == (GUF_DICT_KV_META_IDX_MAX + 1));
guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function dict_try_insert: dict has reached its max size 33,554,430 (or 2^48 - 2 for GUF_DICT_64_BIT_IDX)"));
return;
}
// 1.) Grow kv-index-buffer if neccessary (or make the initial allocation.)
GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary_)(ht, false, 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.
const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key);
bool key_exists = false;
size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &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 (GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx])) {
ht->num_tombstones -= 1;
GUF_ASSERT_RELEASE(ht->num_tombstones >= 0);
}
GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)ht->kv_elems.size) == 0);
ht->kv_indices[idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)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;
}
const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key);
bool key_exists = false;
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &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 = GUF_DICT_META_GET_IDX(ht->kv_indices[idx]);
GUF_ASSERT(kv_idx <= PTRDIFF_MAX && (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 GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key);
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &key_exists);
if (key_exists) {
GUF_ASSERT(idx != SIZE_MAX);
GUF_ASSERT(!GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx]));
GUF_ASSERT(!GUF_DICT_META_IS_NULL(ht->kv_indices[idx]));
}
(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;
}
const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key);
bool key_exists = false;
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, key, key_hash, &key_exists);
if (!key_exists) {
return false;
}
GUF_ASSERT(idx < SIZE_MAX && (ptrdiff_t)idx < ht->kv_indices_cap);
const size_t kv_idx = (size_t)GUF_DICT_META_GET_IDX(ht->kv_indices[idx]);
GUF_ASSERT(kv_idx < (size_t)ht->kv_elems.size);
ht->kv_indices[idx] = GUF_DICT_KV_META_IDX_TOMBSTONE;
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.
GUF_ASSERT(kv_idx <= GUF_DICT_KV_META_IDX_MAX);
// 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.
const GUF_DICT_HASH_T last_key_hash = GUF_DICT_KEY_HASH(&last_kv->key);
bool last_key_exists = false;
const size_t last_idx = GUF_CAT(GUF_DICT_NAME, _find_idx_)(ht, &last_kv->key, last_key_hash, &last_key_exists);
GUF_ASSERT(last_idx != idx);
GUF_ASSERT(last_key_exists && (ptrdiff_t)last_idx < ht->kv_indices_cap);
GUF_ASSERT(GUF_DICT_META_GET_IDX(ht->kv_indices[last_idx]) == (GUF_DICT_KV_META_T)(ht->kv_elems.size - 1));
ht->kv_indices[last_idx] = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[last_idx]) | (GUF_DICT_KV_META_T)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 (TODO: not sure if actually a good idea...)
ptrdiff_t del_tombstone_cnt = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap && del_tombstone_cnt < ht->num_tombstones; ++i) {
const GUF_DICT_KV_META_T kv_del_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[i]);
GUF_ASSERT(GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[i]) == 0);
GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE || kv_del_idx == GUF_DICT_KV_META_IDX_NULL);
if (kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) {
ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL;
++del_tombstone_cnt;
} else {
GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_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);
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_shrink_to_fit)(GUF_DICT_NAME *ht, guf_err *err)
{
GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _valid)(ht));
GUF_CAT(GUF_DICT_KV_DBUF, _try_shrink_to_fit)(&ht->kv_elems, err);
}
GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _shrink_to_fit)(GUF_DICT_NAME *ht)
{
GUF_CAT(GUF_DICT_NAME, _try_shrink_to_fit)(ht, NULL);
}
/* 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_META_T
#undef GUF_DICT_HASH_T
#undef GUF_DICT_HASH_T_MAX
#undef GUF_DICT_32_BIT_HASH
#undef GUF_DICT_64_BIT_HASH
#undef GUF_DICT_64_BIT_IDX
#undef GUF_DICT_HASH_T_GET_HASHFRAG
#undef GUF_DICT_KV_META_HASHFRAG_MASK
#undef GUF_DICT_KV_META_IDX_MASK
#undef GUF_DICT_KV_META_IDX_NULL
#undef GUF_DICT_KV_META_IDX_TOMBSTONE
#undef GUF_DICT_KV_META_IDX_MAX
#undef GUF_DICT_META_GET_IDX
#undef GUF_DICT_META_GET_HASHFRAG
#undef GUF_DICT_META_IS_NULL
#undef GUF_DICT_META_IS_TOMBSTONE
#undef GUF_DICT_NAME
#undef GUF_DICT_IS_SET
#undef GUF_DICT_PROBE_LINEAR
#undef GUF_DICT_PROBE_QUADRATIC
#undef GUF_DICT_MAX_LOAD_FACTOR
#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