Last active
May 24, 2024 13:03
-
-
Save RandyGaul/b41be8fba14680a65f6e29aa093e7517 to your computer and use it in GitHub Desktop.
Polymorphic C hash table macros
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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