Last active
November 29, 2025 07:43
-
-
Save leok7v/a8fd17d649a4de6673c40c0fefda327f to your computer and use it in GitHub Desktop.
Simple (almost trivial) map. Subset of C++ std::map good enough to limited usage for str::map<std::string,something> replacement
This file contains hidden or 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
| #include "map.h" | |
| #include <assert.h> | |
| #include <limits.h> | |
| #include <math.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #undef MAP_DUMP_CONVERT | |
| #define MAP_MIN_CAPACITY 8 | |
| #define MAP_LOAD_FACTOR_NUM 3 // numerator | |
| #define MAP_LOAD_FACTOR_DEN 4 // denominator | |
| static inline const struct map_kv* map_convert(const struct map_kv * p) { | |
| #if defined(DEBUG) && defined(MAP_DUMP_CONVERT) | |
| switch (p->kind) { | |
| case MAP_KIND_INT: | |
| fprintf(stderr, "int: %llu %lld\n", | |
| (unsigned long long)p->i, p->i); | |
| break; | |
| case MAP_KIND_FLT: | |
| fprintf(stderr, "float: %f\n", (double)*(float*)&p->i); | |
| break; | |
| case MAP_KIND_DBL: | |
| fprintf(stderr, "double: %f\n", *(double*)&p->i); | |
| break; | |
| case MAP_KIND_STR: | |
| fprintf(stderr, "str: \"%s\"\n", *(char**)&p->i); | |
| break; | |
| default: | |
| fprintf(stderr, "unsuported kind: %d\n", p->kind); | |
| assert(false); | |
| exit(1); | |
| } | |
| #endif | |
| return p; | |
| } | |
| static size_t next_power_of_two(size_t c) { | |
| if (c == 0) return 1; | |
| c--; | |
| c |= c >> 1; | |
| c |= c >> 2; | |
| c |= c >> 4; | |
| c |= c >> 8; | |
| c |= c >> 16; | |
| #if SIZE_MAX > UINT32_MAX | |
| c |= c >> 32; | |
| #endif | |
| c++; | |
| return c; | |
| } | |
| static int32_t map_hash(const void * data, size_t bytes) { | |
| const uint8_t* p = (const uint8_t*)data; | |
| uint32_t h = 2166136261u; | |
| for (size_t i = 0; i < bytes; i++) { | |
| h ^= p[i]; | |
| h *= 16777619u; | |
| } | |
| h &= INT32_MAX; // get rid of sign bit | |
| if (h == 0u) { h = 1u; } // never zero | |
| return (int32_t)h; | |
| } | |
| static inline bool is_str(enum map_kv_kind kind) { | |
| return kind == MAP_KIND_STR; | |
| } | |
| static inline bool is_float(enum map_kv_kind kind) { | |
| return kind == MAP_KIND_FLT; | |
| } | |
| static inline bool is_double(enum map_kv_kind kind) { | |
| return kind == MAP_KIND_DBL; | |
| } | |
| static void check_kv(const struct map_kv * kv, enum map_kv_kind kind) { | |
| if ((kv->kind != kind) || | |
| (is_str(kind) && (char*)(uintptr_t)kv->i == NULL) || | |
| (is_float(kind) && !isfinite(*(float*)&kv->i)) || | |
| (is_double(kind) && !isfinite(*(double*)&kv->i)) | |
| ) { | |
| assert(false); | |
| fprintf(stderr, "FATAL: invalid key or value\n"); | |
| abort(); | |
| } | |
| } | |
| static inline char * kv_data(const struct map_kv * kv) { | |
| return is_str(kv->kind) ? (char*)(uintptr_t)kv->i : (char*)&kv->i; | |
| } | |
| static inline size_t kv_bytes(const struct map_kv * kv, size_t size) { | |
| return is_str(kv->kind) ? strlen((char*)(uintptr_t)kv->i) : size; | |
| } | |
| static bool kv_dup(struct map_kv * kv) { | |
| if (is_str(kv->kind)) { | |
| char * d = strdup(kv_data(kv)); | |
| if (d == NULL) { return false; } | |
| kv->i = (uintptr_t)d; | |
| } | |
| return true; | |
| } | |
| static void kv_free(struct map_kv * kv) { | |
| if (is_str(kv->kind)) { | |
| free(kv_data(kv)); | |
| *(char**)(uintptr_t*)&kv->i = NULL; | |
| } | |
| } | |
| static int32_t _map_find_slot(const struct _map * m, | |
| const char * data, size_t bytes, | |
| enum map_kv_kind kind, size_t ks, | |
| int32_t h, bool * found) { | |
| assert(h > 0); | |
| int32_t cap = m->capacity; | |
| *found = false; | |
| if (cap <= 0) { return -1; } | |
| // Ensure power-of-2 (for probing coverage and & optimization) | |
| assert(cap > 0 && (cap & (cap - 1)) == 0); | |
| int32_t first_tomb = -1; | |
| uint32_t start = (uint32_t)h & (uint32_t)(cap - 1); | |
| for (int32_t k = 0; k < cap; k++) { | |
| uint32_t offset = (uint32_t)((uint64_t)k * ((uint64_t)k + 1) / 2); | |
| int32_t i = (int32_t)((start + offset) & (uint32_t)(cap - 1)); | |
| int32_t hv = m->hash[i]; | |
| if (hv == 0) { | |
| return first_tomb >= 0 ? first_tomb : i; | |
| } | |
| if (hv < 0) { | |
| if (first_tomb < 0) { first_tomb = i; } | |
| } else if (hv == h) { | |
| char* key = m->keys + (size_t)i * ks; | |
| if (is_str(kind) ? strcmp(*(char**)key, data) == 0 : | |
| memcmp(key, data, bytes) == 0) { | |
| *found = true; | |
| return i; | |
| } | |
| } | |
| } | |
| return first_tomb >= 0 ? first_tomb : -1; | |
| } | |
| bool _map_alloc(struct _map* m, int32_t capacity, size_t ks, size_t vs) { | |
| assert(m); | |
| m->keys = NULL; | |
| m->vals = NULL; | |
| m->hash = NULL; | |
| m->count = 0; | |
| m->capacity = 0; | |
| if (capacity <= 0) { return true; } // will grow on first map_put() | |
| assert((size_t)capacity <= SIZE_MAX / 2); | |
| if ((size_t)capacity > SIZE_MAX / 2) { return false; } | |
| size_t cap = next_power_of_two((size_t)capacity); | |
| assert(cap <= INT32_MAX && cap <= SIZE_MAX / ks && cap <= SIZE_MAX / vs); | |
| if (cap > INT32_MAX || cap > SIZE_MAX / ks || cap > SIZE_MAX / vs) { | |
| return false; | |
| } | |
| void* keys = malloc(cap * ks); | |
| if (!keys) { return false; } | |
| void* vals = malloc(cap * vs); | |
| if (!vals) { free(keys); return false; } | |
| int* hash = (int*)calloc(cap, sizeof(m->hash[0])); // .zero init | |
| if (!hash) { free(keys); free(vals); return false; } | |
| m->keys = keys; | |
| m->vals = vals; | |
| m->hash = hash; | |
| m->capacity = (int32_t)cap; | |
| return true; | |
| } | |
| static bool _map_rehash_into(struct _map * dst, | |
| const struct _map * src, | |
| enum map_kv_kind kind, // key's kind | |
| size_t ks, size_t vs) { | |
| const bool key_is_str = is_str(kind); | |
| int32_t cap = src->capacity; | |
| int32_t i = 0; | |
| while (i < cap) { | |
| int32_t h = src->hash[i]; | |
| if (h > 0) { | |
| char* key = src->keys + (size_t)i * ks; | |
| char* val = src->vals + (size_t)i * vs; | |
| char* data = key_is_str ? *(char**)key : key; | |
| size_t bytes = key_is_str ? strlen(data) : ks; | |
| bool found = false; | |
| int32_t slot = _map_find_slot(dst, data, bytes, | |
| kind, ks, h, &found); | |
| if (slot < 0 || found) { return false; } // oom | |
| memcpy(dst->keys + (size_t)slot * ks, key, ks); | |
| memcpy(dst->vals + (size_t)slot * vs, val, vs); | |
| dst->hash[slot] = h; | |
| dst->count++; | |
| } | |
| i++; | |
| } | |
| return true; | |
| } | |
| static void _map_free_deallocate(struct _map * m); | |
| static bool _map_resize(struct _map * m, int32_t capacity, | |
| enum map_kv_kind k_kind, | |
| size_t ks, size_t vs) { | |
| if (capacity > INT32_MAX) { return false; } | |
| int32_t was = m->capacity; | |
| int32_t cap = MAP_MIN_CAPACITY; | |
| while (cap < capacity) { | |
| if (cap >= INT32_MAX / 2) { cap = INT32_MAX; break; } | |
| cap *= 2; | |
| } | |
| if (cap == was) { return true; } | |
| if (cap / MAP_LOAD_FACTOR_DEN <= m->count / MAP_LOAD_FACTOR_NUM) { | |
| return false; // cannot shrink smaller than count * 4 / 3 | |
| } | |
| struct _map map = {}; | |
| if (!_map_alloc(&map, cap, ks, vs)) { | |
| return false; | |
| } | |
| if (was > 0 && m->keys && m->vals && m->hash) { | |
| if (!_map_rehash_into(&map, m, k_kind, ks, vs)) { | |
| _map_free_deallocate(&map); | |
| return false; | |
| } | |
| } | |
| _map_free_deallocate(m); | |
| m->keys = map.keys; | |
| m->vals = map.vals; | |
| m->hash = map.hash; | |
| m->count = map.count; | |
| m->capacity = map.capacity; | |
| return true; | |
| } | |
| bool _map_grow(struct _map * m, int32_t capacity, | |
| enum map_kv_kind k_kind, | |
| size_t ks, size_t vs) { | |
| return capacity > m->capacity ? | |
| _map_resize(m, capacity, k_kind, ks, vs) : true; | |
| } | |
| bool _map_shrink(struct _map * m, int32_t capacity, | |
| enum map_kv_kind k_kind, | |
| size_t ks, size_t vs) { | |
| return m->count < capacity && capacity < m->capacity ? | |
| _map_resize(m, capacity, k_kind, ks, vs) : true; | |
| } | |
| bool _map_put(struct _map* m, | |
| struct map_kv key, struct map_kv val, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind, | |
| size_t ks, size_t vs) { | |
| assert(m); | |
| check_kv(&key, k_kind); | |
| check_kv(&val, v_kind); | |
| // const bool key_is_str = is_str(key.kind); | |
| // const bool val_is_str = is_str(val.kind); | |
| if (!kv_dup(&val)) { return false; } | |
| if (m->capacity <= 0 || (m->count + 1) * MAP_LOAD_FACTOR_DEN > | |
| m->capacity * MAP_LOAD_FACTOR_NUM) { | |
| int32_t cap; | |
| if (m->capacity < MAP_MIN_CAPACITY) { | |
| cap = MAP_MIN_CAPACITY; | |
| } else if (m->capacity <= INT32_MAX / 2) { | |
| cap = m->capacity * 2; | |
| } else { | |
| cap = INT32_MAX; | |
| } | |
| if (!_map_grow(m, cap, k_kind, ks, vs)) { | |
| kv_free(&val); | |
| return false; | |
| } | |
| } | |
| const void* data = kv_data(&key); | |
| const size_t bytes = kv_bytes(&key, ks); | |
| const int32_t h = map_hash(data, bytes); | |
| bool found = 0; | |
| int32_t slot = _map_find_slot(m, data, bytes, k_kind, ks, h, &found); | |
| if (slot < 0) { | |
| return false; | |
| } | |
| if (found) { | |
| if (is_str(v_kind)) { | |
| char* was = *(char**)(m->vals + (size_t)slot * vs); | |
| if (was) { free(was); } | |
| } | |
| memcpy(m->vals + (size_t)slot * vs, &val.i, vs); | |
| } else { | |
| if (!kv_dup(&key)) { kv_free(&val); return false; } | |
| memcpy(m->keys + (size_t)slot * ks, &key.i, ks); | |
| memcpy(m->vals + (size_t)slot * vs, &val.i, vs); | |
| m->hash[slot] = h; | |
| m->count++; | |
| } | |
| return true; | |
| } | |
| int32_t _map_idx(const struct _map * m, | |
| struct map_kv key, enum map_kv_kind kind, size_t size) { | |
| assert(m); | |
| check_kv(&key, kind); | |
| int32_t r = -1; | |
| if (m->capacity > 0 && m->count > 0) { | |
| const void* data = kv_data(&key); | |
| const size_t bytes = kv_bytes(&key, size); | |
| int32_t h = map_hash(data, bytes); | |
| bool found = false; | |
| int32_t slot = _map_find_slot(m, data, bytes, | |
| kind, size, h, &found); | |
| if (slot >= 0 && found) { | |
| r = slot; | |
| } | |
| } | |
| return r; | |
| } | |
| bool _map_has(const struct _map * m, struct map_kv k, | |
| enum map_kv_kind k_kind, size_t k_size) { | |
| return _map_idx(m, k, k_kind, k_size) != -1; | |
| } | |
| void* _map_get(const struct _map* m, struct map_kv k, | |
| enum map_kv_kind k_kind, size_t ks, size_t vs) { | |
| int32_t index = _map_idx(m, k, k_kind, ks); | |
| return index < 0 ? NULL : m->vals + (size_t)index * vs; | |
| } | |
| bool _map_remove(struct _map* m, struct map_kv key, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind, | |
| size_t ks, size_t vs) { | |
| assert(m); | |
| check_kv(&key, k_kind); | |
| if (m->capacity <= 0 || m->count <= 0) { return false; } | |
| const char* data = kv_data(&key); | |
| const size_t bytes = kv_bytes(&key, ks); | |
| int32_t h = map_hash(data, bytes); | |
| bool found = false; | |
| int32_t slot = _map_find_slot(m, data, bytes, k_kind, ks, h, &found); | |
| if (slot < 0 || !found) { return false; } | |
| if (is_str(k_kind)) { | |
| char* was = *(char**)(m->keys + (size_t)slot * ks); | |
| if (was) { free(was); } | |
| } | |
| if (is_str(v_kind)) { | |
| char* was = *(char**)(m->vals + (size_t)slot * vs); | |
| if (was) { free(was); } | |
| } | |
| m->hash[slot] = -m->hash[slot]; | |
| m->count--; | |
| if (m->count > 0 && m->count < m->capacity / 4) { | |
| (void)_map_shrink(m, m->count * 2, k_kind, ks, vs); // ignore result | |
| } | |
| return true; | |
| } | |
| void _map_clear(struct _map* m, enum map_kv_kind k_kind, | |
| enum map_kv_kind v_kind, size_t ks, size_t vs) { | |
| _map_free(m, k_kind, v_kind); | |
| _map_alloc(m, 0, ks, vs); | |
| } | |
| static void _map_free_deallocate(struct _map* m) { | |
| // Frees the arrays but doesn't free the string data within them; | |
| // That content of strdup() values is freed inside _map_free(). | |
| if (m->keys) { free(m->keys); } | |
| if (m->vals) { free(m->vals); } | |
| if (m->hash) { free(m->hash); } | |
| m->keys = NULL; | |
| m->vals = NULL; | |
| m->hash = NULL; | |
| m->count = 0; | |
| m->capacity = 0; | |
| } | |
| void _map_free(struct _map* m, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind) { | |
| assert(m); | |
| static_assert(sizeof(char*) <= sizeof(char*)); | |
| if (k_kind == MAP_KIND_STR) { | |
| for (int32_t i = 0; i < m->capacity; i++) { | |
| if (m->hash[i] > 0) { | |
| char* s = *(char**)(m->keys + i * sizeof(char*)); | |
| if (s) { free((void*)s); } | |
| } | |
| } | |
| } | |
| if (v_kind == MAP_KIND_STR) { | |
| for (int32_t i = 0; i < m->capacity; i++) { | |
| if (m->hash[i] > 0) { | |
| char* s = *(char**)(m->vals + i * sizeof(char*)); | |
| if (s) { free((void*)s); } | |
| } | |
| } | |
| } | |
| _map_free_deallocate(m); | |
| } | |
| #define _map_convert_int(n, T, UT) \ | |
| struct map_kv _map_kv_##n(T v) { \ | |
| struct map_kv kv = { .i = (uint64_t)(int64_t)v, \ | |
| .kind = MAP_KIND_INT \ | |
| }; \ | |
| return *map_convert(&kv); \ | |
| } \ | |
| struct map_kv _map_kv_u##n(UT v) { \ | |
| struct map_kv kv = { .i = (uint64_t)v, .kind = MAP_KIND_INT \ | |
| }; \ | |
| return *map_convert(&kv); \ | |
| } | |
| _map_convert_int(char, signed char, unsigned char) | |
| _map_convert_int(short, short, unsigned short) | |
| _map_convert_int(int, int, unsigned int) | |
| _map_convert_int(long, long, unsigned long) | |
| _map_convert_int(ll, long long, unsigned long long) | |
| static_assert(sizeof(double) == sizeof(uint64_t), "double must be 64 bits"); | |
| static_assert(sizeof(char*) <= sizeof(uint64_t), "pointers"); | |
| struct map_kv _map_kv_flt(float f) { | |
| struct map_kv kv = {.i = 0, .kind = MAP_KIND_FLT}; | |
| union { float f; uint32_t u; } pun = { .f = (f == 0 ? 0.0f : f) }; | |
| kv.i = pun.u; // zero-extended into low 32 bits logically | |
| return *map_convert(&kv); | |
| } | |
| struct map_kv _map_kv_dbl(double d) { | |
| struct map_kv kv = {.i = 0, .kind = MAP_KIND_DBL}; | |
| union { double d; uint64_t u; } pun = { .d = (d == 0 ? 0.0 : d) }; | |
| kv.i = pun.u; | |
| return *map_convert(&kv); | |
| } | |
| struct map_kv _map_kv_str(const char* s) { | |
| struct map_kv kv = {.kind = MAP_KIND_STR}; | |
| memcpy(&kv.i, &s, sizeof(s)); | |
| return *map_convert(&kv); | |
| } | |
| // smoke tests: | |
| static void map_test_i2i(void) { | |
| struct i2i map_of(int16_t, int32_t); | |
| struct i2i m = {}; | |
| bool ok = map_alloc(&m); assert(ok); | |
| ok = map_put(&m, 10, 42); assert(ok); | |
| ok = map_has(&m, 10); assert(ok); | |
| int* r = map_get(&m, 10); | |
| assert(*r == 42); (void)r; | |
| ok = map_remove(&m, 10); assert(ok); | |
| for (int16_t k = 0; k < 1024; k += 13) { | |
| int32_t v = (int32_t)k * (int32_t)k; | |
| ok = map_put(&m, k, v); assert(ok); | |
| ok = map_has(&m, k); assert(ok); | |
| int* res = map_get(&m, k); assert(res); | |
| assert(*res == v); | |
| } | |
| map_foreach(&m, k, v, { | |
| assert((int32_t)k * (int32_t)k == v); | |
| }); | |
| map_free(&m); | |
| } | |
| static void map_test_s2s(void) { | |
| struct s2s map_of(const char*, const char*); | |
| struct s2s m = {}; | |
| bool ok = map_alloc(&m, 1); assert(ok); | |
| ok = map_put(&m, "hello", "world"); assert(ok); | |
| ok = map_put(&m, "goodbye", "universe"); assert(ok); | |
| ok = map_put(&m, "foo", "bar"); assert(ok); | |
| ok = map_has(&m, "hello"); assert(ok); | |
| ok = map_has(&m, "goodbye"); assert(ok); | |
| const char* rs = *map_get(&m, "hello"); | |
| assert(strcmp(rs, "world") == 0); | |
| rs = *map_get(&m, "goodbye"); | |
| assert(strcmp(rs, "universe") == 0); | |
| ok = map_remove(&m, "foo"); | |
| ok = !map_has(&m, "foo"); assert(ok); | |
| const char** ps = map_get(&m, "foo"); | |
| assert(ps == NULL); | |
| (void)ps; // unused in release | |
| (void)rs; // unused in release | |
| map_free(&m); | |
| } | |
| static void map_test_s2d(void) { | |
| struct str2dbl map_of(const char *, double); | |
| struct str2dbl m = {}; | |
| bool ok = map_alloc(&m); assert(ok); | |
| ok = map_put(&m, "e", 2.71828); assert(ok); | |
| ok = map_put(&m, "pi", 3.14159); assert(ok); | |
| assert(*map_get(&m, "e") == 2.71828); | |
| assert(*map_get(&m, "pi") == 3.14159); | |
| map_free(&m); | |
| } | |
| void map_smoke_test(void) { // not all code paths are covered | |
| map_test_i2i(); | |
| map_test_s2s(); | |
| map_test_s2d(); | |
| } |
This file contains hidden or 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
| #pragma once | |
| #ifndef MAP_H | |
| #define MAP_H | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| #include <stdbool.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| /* | |
| Limited to 2^31 - 1 entries. | |
| Not thread-safe; use external synchronization. | |
| Big-endian is NOT supported. | |
| Keys/Values support: | |
| const char* (always strdup()-ed, NULLs not allowed) | |
| int8_t .. int64_t | |
| uint8_t .. uint64_t | |
| float/double (infinity, NaN are not allowed) | |
| (both -0.0/+0.0 are normalized to +0.0) | |
| map_put(&m, -0.0, -0.0) will result in actual | |
| map_put(&m, +0.0, +0.0) | |
| Modifications during map_foreach() are NOT supported. | |
| Zero terminated strings for values and keys always strdup()-ed. | |
| To achieve values (only!) of structural or arrays types with memory | |
| managed by the caller pointers to struct and array can be cast to | |
| (uintptr_t) type. | |
| Usage example: (results checks are omitted for simplicity: | |
| struct int2int map_of(int, int); | |
| struct int2int m = {}; // important: must be .zero initialized | |
| bool ok = map_alloc(&m); // defaults to 16 slots initialization | |
| ok = map_put(&m, 13, 42); | |
| ok = map_has(&m, 13); | |
| int* r = map_get(&m, 13); | |
| assert(*r == 42); | |
| map_foreach(&m, k, v, { printf("%d: %d\n", k, v); }); | |
| ok = map_grow(&m, 1024); | |
| ok = map_remove(&m, 13); | |
| map_clear(&m); // instead of free/alloc | |
| map_free(&m); | |
| struct str2str map_of(const char*, const char*); | |
| struct str2str s2s = {}; | |
| bool ok = map_alloc(&s2s); | |
| ok = map_put(&s2s, "hello", "world"); | |
| char** s = map_get(&s2s, "hello"); | |
| assert(s); | |
| asser(strcmp(*s, "world") == 0); | |
| map_free(&m); | |
| */ | |
| #if __STDC_VERSION__ < 201112L // macOS C23 -> Swift fails with: 202311L | |
| #error "Requires C11 (preferably C23) or later" | |
| #endif | |
| enum map_kv_kind { | |
| MAP_KIND_NIL = 0, // never used | |
| MAP_KIND_STR = 1, // char * | |
| MAP_KIND_INT = 2, // all integer types | |
| MAP_KIND_FLT = 3, // float | |
| MAP_KIND_DBL = 4, // double | |
| }; | |
| struct map_kv { | |
| /* | |
| union { | |
| float f; | |
| double d; | |
| uint64_t i; | |
| const char* s; | |
| }; | |
| */ | |
| uint64_t i; | |
| enum map_kv_kind kind; | |
| }; | |
| struct _map { | |
| char* keys; | |
| char* vals; | |
| int32_t* hash; | |
| int32_t count; | |
| int32_t capacity; | |
| }; | |
| #define map_of(ktype, vtype) { \ | |
| ktype* keys; \ | |
| vtype* vals; \ | |
| int32_t* hash; \ | |
| int32_t count; \ | |
| int32_t capacity; \ | |
| } | |
| struct map_kv _map_kv_char (signed char v); | |
| struct map_kv _map_kv_short (short v); | |
| struct map_kv _map_kv_int (int v); | |
| struct map_kv _map_kv_long (long v); | |
| struct map_kv _map_kv_ll (long long v); | |
| struct map_kv _map_kv_uchar (unsigned char v); | |
| struct map_kv _map_kv_ushort(unsigned short v); | |
| struct map_kv _map_kv_uint (unsigned int v); | |
| struct map_kv _map_kv_ulong (unsigned long v); | |
| struct map_kv _map_kv_ull (unsigned long long v); | |
| struct map_kv _map_kv_flt (float v); | |
| struct map_kv _map_kv_dbl (double v); | |
| struct map_kv _map_kv_str (const char* s); | |
| #define _map_kv_kind(x) \ | |
| _Generic((x), \ | |
| signed char: MAP_KIND_INT, \ | |
| short: MAP_KIND_INT, \ | |
| int: MAP_KIND_INT, \ | |
| long: MAP_KIND_INT, \ | |
| long long: MAP_KIND_INT, \ | |
| unsigned char: MAP_KIND_INT, \ | |
| unsigned short: MAP_KIND_INT, \ | |
| unsigned int: MAP_KIND_INT, \ | |
| unsigned long: MAP_KIND_INT, \ | |
| unsigned long long: MAP_KIND_INT, \ | |
| float: MAP_KIND_FLT, \ | |
| double: MAP_KIND_DBL, \ | |
| const char*: MAP_KIND_STR, \ | |
| char*: MAP_KIND_STR \ | |
| ) | |
| #define _map_kv(x) \ | |
| _Generic((x), \ | |
| signed char: _map_kv_char, \ | |
| short: _map_kv_short, \ | |
| int: _map_kv_int, \ | |
| long: _map_kv_long, \ | |
| long long: _map_kv_ll, \ | |
| unsigned char: _map_kv_uchar, \ | |
| unsigned short: _map_kv_ushort, \ | |
| unsigned int: _map_kv_uint, \ | |
| unsigned long: _map_kv_ulong, \ | |
| unsigned long long: _map_kv_ull, \ | |
| float: _map_kv_flt, \ | |
| double: _map_kv_dbl, \ | |
| const char*: _map_kv_str, \ | |
| char*: _map_kv_str \ | |
| ) | |
| #define _map_ks(m) sizeof((m)->keys[0]) | |
| #define _map_vs(m) sizeof((m)->vals[0]) | |
| bool _map_alloc (struct _map* m, int32_t capacity, size_t ks, size_t vs); | |
| bool _map_grow (struct _map* m, int32_t min_capacity, | |
| enum map_kv_kind k_kind, size_t ks, size_t vs); | |
| bool _map_shrink (struct _map* m, int32_t min_capacity, | |
| enum map_kv_kind k_kind, size_t ks, size_t vs); | |
| bool _map_put (struct _map* m, struct map_kv k, struct map_kv v, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind, | |
| size_t k_size, size_t v_size); | |
| bool _map_remove (struct _map* m, struct map_kv k, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind, | |
| size_t ks, size_t vs); | |
| bool _map_has (const struct _map* m, struct map_kv k, | |
| enum map_kv_kind k_kind, size_t k_size); | |
| int32_t _map_idx (const struct _map* m, struct map_kv k, | |
| enum map_kv_kind k_kind, size_t k_size); | |
| void* _map_get (const struct _map* m, struct map_kv k, | |
| enum map_kv_kind k_kind, size_t ks, size_t vs); | |
| void _map_clear (struct _map* m, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind, | |
| size_t ks, size_t vs); | |
| void _map_free (struct _map* m, | |
| enum map_kv_kind k_kind, enum map_kv_kind v_kind); | |
| void map_smoke_test(void); // quick and dirty test | |
| void map_test(void); | |
| void map_test_memory_pressure(void); // runs map_test 1,000,000 times | |
| #if __STDC_VERSION__ >= 202311L | |
| #define _map_typeof(t) typeof(t) | |
| #else | |
| #define _map_typeof(t) __typeof__(t) | |
| #endif | |
| #define _map_get_3rd(_1,_2,_3,...) _3 | |
| #define _map_chooser(...) \ | |
| _map_get_3rd(__VA_ARGS__, _map_alloc2, _map_alloc1, _map_bad) | |
| #define map_alloc(...) _map_chooser(__VA_ARGS__)(__VA_ARGS__) | |
| #define _map_alloc1(m) \ | |
| _map_alloc((struct _map*)(m), 16, _map_ks(m), _map_vs(m)) | |
| #define _map_alloc2(m, n) \ | |
| _map_alloc((struct _map*)(m), n, _map_ks(m), _map_vs(m)) | |
| #define map_grow(m, n) \ | |
| _map_grow((struct _map*)(m), n, \ | |
| _map_kv_kind((m)->keys[0]), \ | |
| _map_ks(m), _map_vs(m)) | |
| #define map_shrink(m, n) \ | |
| _map_shrink((struct _map*)(m), n, \ | |
| _map_kv_kind((m)->keys[0]), \ | |
| _map_ks(m), _map_vs(m)) | |
| #define map_put(m, k, v) \ | |
| _map_put((struct _map*)(m), \ | |
| _map_kv(k)(k), \ | |
| _map_kv(v)(v), \ | |
| _map_kv_kind(k), \ | |
| _map_kv_kind(v), \ | |
| _map_ks(m), _map_vs(m)) | |
| #define map_has(m, k) \ | |
| _map_has((struct _map*)(m), _map_kv(k)(k), _map_kv_kind(k), _map_ks(m)) | |
| #define map_get(m, k) ( \ | |
| _map_typeof((m)->vals)) ( \ | |
| _map_get((struct _map*)(m), \ | |
| _map_kv(k)(k), \ | |
| _map_kv_kind(k), \ | |
| _map_ks(m), \ | |
| _map_vs(m)) \ | |
| ) | |
| #define map_remove(m, k) \ | |
| _map_remove((struct _map*)(m), \ | |
| _map_kv(k)(k), \ | |
| _map_kv_kind(k), \ | |
| _map_kv_kind((m)->vals[0]), \ | |
| _map_ks(m), _map_vs(m)) | |
| #define map_clear(m) \ | |
| _map_clear((struct _map*)(m), \ | |
| _map_kv_kind((m)->keys[0]), \ | |
| _map_kv_kind((m)->vals[0]), \ | |
| _map_ks(m), _map_vs(m)) | |
| #define map_free(m) \ | |
| _map_free((struct _map*)(m), \ | |
| _map_kv_kind((m)->keys[0]), \ | |
| _map_kv_kind((m)->vals[0])) | |
| #define map_foreach(m, k_var, v_var, code) do { \ | |
| for(int32_t _i = 0; _i < (m)->capacity; _i++) { \ | |
| if ((m)->hash[_i] > 0) { \ | |
| _map_typeof((m)->keys[0]) k_var = (m)->keys[_i]; \ | |
| _map_typeof((m)->vals[0]) v_var = (m)->vals[_i]; \ | |
| code; \ | |
| } \ | |
| } \ | |
| } while(0) | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* MAP_H */ |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
map_test.c
is here:
https://gist.github.com/leok7v/25ad567d49e6a6ceeeee4aa8dfdb2d9f