#include #include #include #include "hashmap.h" #include "resizable.h" struct bucket { void *keys; void *values; size_t len; size_t cap; }; struct apfl_hashmap_struct { struct apfl_hashmap_callbacks callbacks; size_t keysize; size_t valsize; size_t nbuckets; struct bucket *buckets; }; struct apfl_hashmap_cursor_struct { apfl_hashmap map; size_t bucket; size_t i; }; #define FNV_PRIME 1099511628211U apfl_hash apfl_hash_fnv1a_add(const void *data, size_t len, apfl_hash hash) { for (size_t i = 0; i < len; i++) { uint8_t byte = ((uint8_t *)data)[i]; hash ^= byte; hash *= FNV_PRIME; } return hash; } apfl_hash apfl_hash_fnv1a(const void *data, size_t len) { return apfl_hash_fnv1a_add(data, len, APFL_HASH_FNV1A_INIT); } #define HAS_CALLBACK(map, cb) ((map)->callbacks.cb != NULL) #define INVOKE_CALLBACK(map, cb, ...) (map)->callbacks.cb((map)->callbacks.opaque, __VA_ARGS__) static bool keys_eq(apfl_hashmap map, const void *a, const void *b) { if (HAS_CALLBACK(map, keys_eq)) { return INVOKE_CALLBACK(map, keys_eq, a, b); } else { return memcmp(a, b, map->keysize) == 0; } } static apfl_hash calc_hash(apfl_hashmap map, const void *key) { if (HAS_CALLBACK(map, calc_hash)) { return INVOKE_CALLBACK(map, calc_hash, key); } else { return apfl_hash_fnv1a(key, map->keysize); } } static void destroy_key(apfl_hashmap map, void *key) { if (HAS_CALLBACK(map, destroy_key)) { INVOKE_CALLBACK(map, destroy_key, key); } } static void destroy_value(apfl_hashmap map, void *value) { if (HAS_CALLBACK(map, destroy_value)) { INVOKE_CALLBACK(map, destroy_value, value); } } static bool copy_key(apfl_hashmap map, void *dest, const void *src) { if (HAS_CALLBACK(map, copy_key)) { return INVOKE_CALLBACK(map, copy_key, dest, src); } else { memcpy(dest, src, map->keysize); return true; } } static bool copy_value(apfl_hashmap map, void *dest, const void *src) { if (HAS_CALLBACK(map, copy_value)) { return INVOKE_CALLBACK(map, copy_value, dest, src); } else { memcpy(dest, src, map->valsize); return true; } } #define CAP_GROW 5 static_assert(CAP_GROW >= 1, "CAP_GROW must be at least 1"); static size_t calc_new_cap(size_t old_cap) { return old_cap + CAP_GROW; } #define KVADDR(base, elemsize, off) (((char*)(base)) + ((elemsize)*(off))) static bool find_key_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key, size_t *off) { size_t keysize = map->keysize; for (size_t i = 0; i < bucket->len; i++) { if (keys_eq(map, key, KVADDR(bucket->keys, keysize, i))) { *off = i; return true; } } return false; } static bool set_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key, const void *value) { size_t keysize = map->keysize; size_t valsize = map->valsize; size_t i; if (find_key_in_bucket(map, bucket, key, &i)) { void *dest = KVADDR(bucket->values, valsize, i); destroy_value(map, dest); return copy_value(map, dest, value); } if (bucket->len <= bucket->cap) { size_t new_cap = calc_new_cap(bucket->cap); void *newmem; newmem = realloc(bucket->keys, new_cap * keysize); if (newmem == NULL) { return false; } bucket->keys = newmem; newmem = realloc(bucket->values, new_cap * valsize); if (newmem == NULL) { return false; } bucket->values = newmem; bucket->cap = new_cap; } if (!copy_key(map, KVADDR(bucket->keys, keysize, bucket->len), key)) { return false; } if (!copy_value(map, KVADDR(bucket->values, valsize, bucket->len), value)) { destroy_key(map, KVADDR(bucket->keys, keysize, bucket->len)); return false; } bucket->len++; return true; } static bool get_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key, void *value) { size_t i; if (!find_key_in_bucket(map, bucket, key, &i)) { return false; } if (value != NULL) { size_t valsize = map->valsize; if (!copy_value(map, value, KVADDR(bucket->values, valsize, i))) { return false; // TODO: This way, we cant distinguish an error in copy_value from a non-set key } } return true; } static struct bucket * bucket_by_key(apfl_hashmap map, const void *key) { apfl_hash hash = calc_hash(map, key); return &map->buckets[hash % map->nbuckets]; } static void delete_in_bucket(apfl_hashmap map, struct bucket *bucket, const void *key) { size_t i; if (!find_key_in_bucket(map, bucket, key, &i)) { return; } size_t keysize = map->keysize; size_t valsize = map->valsize; destroy_key(map, KVADDR(bucket->keys, keysize, i)); destroy_value(map, KVADDR(bucket->values, valsize, i)); assert(bucket->len >= (i+1)); memmove( KVADDR(bucket->keys, keysize, i), KVADDR(bucket->keys, keysize, i+1), (bucket->len - (i+1)) * keysize ); memmove( KVADDR(bucket->values, valsize, i), KVADDR(bucket->values, valsize, i+1), (bucket->len - (i+1)) * valsize ); assert(bucket->len > 0); // if len == 0, we would not have found an entry bucket->len--; } #define INITIAL_NBUCKETS 16 // Must be a power of 2 apfl_hashmap apfl_hashmap_new(struct apfl_hashmap_callbacks callbacks, size_t keysize, size_t valsize) { apfl_hashmap map = malloc(sizeof(struct apfl_hashmap_struct)); if (map == NULL) { goto fail; } map->callbacks = callbacks; map->keysize = keysize; map->valsize = valsize; map->nbuckets = INITIAL_NBUCKETS; map->buckets = malloc(sizeof(struct bucket) * INITIAL_NBUCKETS); if (map->buckets == NULL) { goto fail; } for (size_t i = 0; i < INITIAL_NBUCKETS; i++) { map->buckets[i] = (struct bucket) { .keys = NULL, .values = NULL, .len = 0, .cap = 0, }; } return map; fail: free(map); return NULL; } void apfl_hashmap_delete(apfl_hashmap map, const void *key) { delete_in_bucket(map, bucket_by_key(map, key), key); } bool apfl_hashmap_get(apfl_hashmap map, const void *key, void *value) { return get_in_bucket(map, bucket_by_key(map, key), key, value); } bool apfl_hashmap_set(apfl_hashmap map, const void *key, const void *value) { return set_in_bucket(map, bucket_by_key(map, key), key, value); } static void destroy_bucket(apfl_hashmap map, struct bucket *bucket) { for (size_t i = 0; i < bucket->len; i++) { destroy_key(map, KVADDR(bucket->keys, map->keysize, i)); destroy_value(map, KVADDR(bucket->values, map->valsize, i)); } free(bucket->keys); free(bucket->values); bucket->len = 0; bucket->cap = 0; } void apfl_hashmap_destroy(apfl_hashmap map) { if (map == NULL) { return; } if (map->buckets != NULL) { for (size_t i = 0; i < map->nbuckets; i++) { destroy_bucket(map, &map->buckets[i]); } free(map->buckets); } free(map); } static void cursor_skip_empty_buckets(apfl_hashmap_cursor cur) { apfl_hashmap map = cur->map; while (cur->bucket < map->nbuckets && map->buckets[cur->bucket].len == 0) { cur->bucket++; } } apfl_hashmap_cursor apfl_hashmap_get_cursor(apfl_hashmap map) { apfl_hashmap_cursor cursor = malloc(sizeof(struct apfl_hashmap_cursor_struct)); if (cursor != NULL) { cursor->map = map; cursor->i = 0; cursor->bucket = 0; cursor_skip_empty_buckets(cursor); } return cursor; } bool apfl_hashmap_cursor_is_end(apfl_hashmap_cursor cursor) { return cursor->bucket >= cursor->map->nbuckets; } static struct bucket * cursor_get_bucket(apfl_hashmap_cursor cursor) { return apfl_hashmap_cursor_is_end(cursor) ? NULL : &cursor->map->buckets[cursor->bucket]; } void apfl_hashmap_cursor_next(apfl_hashmap_cursor cursor) { struct bucket *bucket = cursor_get_bucket(cursor); if (bucket == NULL) { return; // End already reached } cursor->i++; if (cursor->i < bucket->len) { return; } cursor->bucket++; cursor->i = 0; cursor_skip_empty_buckets(cursor); } #define CURSOR_GET(cursor, out, bucketmemb, sizememb, copy) \ struct bucket *bucket = cursor_get_bucket(cursor); \ \ if (bucket == NULL) { \ return false; /* End already reached */ \ } \ \ if (cursor->i >= bucket->len) { \ return false; \ } \ \ size_t size = cursor->map->sizememb; \ \ return copy( \ cursor->map, \ out, \ KVADDR(bucket->bucketmemb, size, bucket->len) \ ); \ bool apfl_hashmap_cursor_get_key(apfl_hashmap_cursor cursor, void *key) { CURSOR_GET(cursor, key, keys, keysize, copy_key) } bool apfl_hashmap_cursor_get_value(apfl_hashmap_cursor cursor, void *value) { CURSOR_GET(cursor, value, values, valsize, copy_value) }