Skip to content

Instantly share code, notes, and snippets.

@RandyGaul
Last active May 24, 2024 13:03
Show Gist options
  • Save RandyGaul/b41be8fba14680a65f6e29aa093e7517 to your computer and use it in GitHub Desktop.
Save RandyGaul/b41be8fba14680a65f6e29aa093e7517 to your computer and use it in GitHub Desktop.
Polymorphic C hash table macros
// Polymorphic hashtable API in C
//
// We get value semantics here for hset and hget (see comments below).
// An extra layer of indirection is internally used so we can sort {k,v} pairs, e.g. for priority queue.
// The main trick is the use of comma in C. Any statement in C takes on the value of the expression
// in the final comma, not any of the prior. We can use that to "return" a value in `hget` while running
// a few expressions beforehand. Since the user type is a pointer to a value we get polymorphism by
// the indirection or array operator `*h` or `h[0]`.
//
// Original inspired from these resources by Per Vognsen and Micha Mettke
// https://gist.github.com/pervognsen/d754ab6729f8393307e15cba19f7b452
// https://github.com/vurtun/tau/blob/main/src/lib/std.c
//
// Note - CF stands for Cute Framework where this code snippet came from.
// https://github.com/RandyGaul/cute_framework
// Example usage:
void Example()
{
v2* h = NULL;
hset(h, 0, V2(1, 2));
hset(h, 1, V2(4, 10));
hset(h, 2, V2(-12, 13));
v2 a = hget(h, 0);
v2 b = hget(h, 1);
v2 c = hget(h, 2);
assert(a.x == 1 && a.y == 2);
assert(b.x == 4 && b.y == 10);
assert(c.x == -12 && c.y == 13);
hfree(h);
}
// htable API
//
// hset(h, k, ...) // k is typecasted to `uint64_t`
// hget(h, k) // Returns an r-value, you can do: my_val = hget(h, k)
// hclear(h)
// hkeys(h) // Return an array of all the keys (read only)
// hitems(h) // Return an array of all the items, or v's for each {k,v} pair (read only)
// hswap(h, index_a, index_b) // Can swap any {k,v} pair without ruining the hashing. Can implement e.g. priority queue here.
// hfree(h)
// "Hidden" header beyond the user's pointer
typedef struct cf_hhdr_t
{
size_t item_size;
cf_hashtable_t table;
uint32_t cookie;
} cf_hhdr_t;
// Internal macros.
#define hhdr(h) ((cf_hhdr_t*)h - 1)
#define HCOOKIE 0xE6F7E359
#define HCANARY(h) (h ? CUTE_ASSERT(hhdr(h)->cookie == HCOOKIE) : 0) // Extra check for safety.
// Hashtable API implementation
#define hset(h, k, ...) do { h ? h : (*(void**)&h = cf_hmake(sizeof(*h))); HCANARY(h); h[0] = (__VA_ARGS__); uint64_t key = (uint64_t)k; cf_hashtable_insert(&(hhdr(h)->table), &key, h); } while (0)
#define hget(h, k) (HCANARY(h), cf_hget(h, (uint64_t)k), h[0])
#define hclear(h) (HCANARY(h), cf_hashtable_clear(h))
#define hkeys(h) (HCANARY(h), (uint64_t*)cf_hashtable_keys(h))
#define hitems(h) (HCANARY(h), cf_hashtable_items(h))
#define hswap(h, index_a, index_b) (HCANARY(h), cf_hashtable_swap(h, index_a, index_b))
#define hfree(h) do { HCANARY(h); if (h) { cf_hhdr_t* hdr = hhdr(h); cf_hashtable_cleanup(&hdr->table); CUTE_FREE(hdr, NULL); } h = NULL; } while (0)
CUTE_INLINE void cf_hget(void* h, uint64_t k)
{
cf_hhdr_t* hdr = hhdr(h);
void* item = cf_hashtable_find(&hdr->table, (void*)&k);
if (item) CUTE_MEMCPY(h, item, hdr->item_size);
else CUTE_MEMSET(h, 0, hdr->item_size);
}
CUTE_INLINE void* cf_hmake(int item_size)
{
cf_hhdr_t* hdr = (cf_hhdr_t*)CUTE_ALLOC(sizeof(cf_hhdr_t) + item_size, NULL);
hdr->item_size = item_size;
cf_hashtable_init(&hdr->table, sizeof(uint64_t), item_size, 32, NULL);
hdr->cookie = HCOOKIE;
return hdr + 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment