1053 lines
46 KiB
C
Executable File
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
|