diff options
Diffstat (limited to 'src/hashmap.c')
| -rwxr-xr-x | src/hashmap.c | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100755 index 0000000..7bb3199 --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,390 @@ +#include <bh/hashmap.h> +#include <stdlib.h> +#include <string.h> + + +typedef struct bh_hashmap_node_s +{ + void *key; + void *value; +} bh_hashmap_node_t; + + +struct bh_hashmap_s +{ + bh_hashmap_node_t *data; + size_t *psls; + size_t size; + size_t capacity; + size_t threshold; + bh_equal_cb_t equal; + bh_hash_cb_t hash; + float factor; +}; + + +static void bh_hashmap_init(bh_hashmap_t *hashmap, + bh_equal_cb_t equal, + bh_hash_cb_t hash) +{ + memset(hashmap, 0, sizeof(*hashmap)); + hashmap->factor = 0.75f; + hashmap->equal = equal; + hashmap->hash = hash; +} + + +static void bh_hashmap_destroy(bh_hashmap_t *hashmap) +{ + if (hashmap->capacity) + { + free(hashmap->data); + free(hashmap->psls); + } +} + + +static int calc_capacity(size_t size, + float factor, + size_t *capacity, + size_t *threshold) +{ + /* Check if we need any capacity at all */ + if (!size) + { + *capacity = 0; + *threshold = 0; + return BH_OK; + } + + /* Calculate nearest power of 2 capacity */ + *capacity = 16; + *threshold = *capacity * factor; + while (size > *threshold) + { + *capacity *= 2; + *threshold = *capacity * factor; + + /* Catch capacity overflow */ + if (*capacity < 16) + return BH_OOM; + } + + /* Catch malloc overflow */ + if (*capacity >= ((size_t)-1) / sizeof(bh_hashmap_node_t)) + return BH_OOM; + + return BH_OK; +} + + +static void copy_hashmap(bh_hashmap_t *dest, + bh_hashmap_t *src) +{ + void *iter; + + /* Iterate and insert data into hashmap */ + iter = bh_hashmap_iter_next(src, NULL); + while (iter) + { + void *key, *value; + + key = bh_hashmap_iter_key(iter); + value = bh_hashmap_iter_value(iter); + bh_hashmap_insert(dest, key, value); + + iter = bh_hashmap_iter_next(src, iter); + } +} + + +bh_hashmap_t *bh_hashmap_new(bh_equal_cb_t equal, + bh_hash_cb_t hash) +{ + bh_hashmap_t *result; + + result = malloc(sizeof(*result)); + if (result) + bh_hashmap_init(result, equal, hash); + + return result; +} + + +void bh_hashmap_free(bh_hashmap_t *hashmap) +{ + bh_hashmap_destroy(hashmap); + free(hashmap); +} + + +void bh_hashmap_clear(bh_hashmap_t *hashmap) +{ + if (hashmap->capacity) + memset(hashmap->psls, 0, hashmap->capacity * sizeof(size_t)); + hashmap->size = 0; +} + + +int bh_hashmap_reserve(bh_hashmap_t *hashmap, + size_t size) +{ + bh_hashmap_t other; + size_t capacity, threshold; + + /* New capacity can't be smaller then current hashmap size */ + if (size < hashmap->size) + size = hashmap->size; + + /* Calculate new capacity */ + if (calc_capacity(size, hashmap->factor, &capacity, &threshold)) + return BH_OOM; + + /* Prevent same size reallocation */ + if (capacity == hashmap->capacity) + return BH_OK; + + /* Initialize new hashmap */ + bh_hashmap_init(&other, hashmap->equal, hashmap->hash); + other.factor = hashmap->factor; + + if (capacity) + { + /* Allocate new memory for the hashmap */ + other.data = malloc(sizeof(*other.data) * capacity); + other.psls = malloc(sizeof(size_t) * capacity); + other.threshold = threshold; + other.capacity = capacity; + + /* Check for allocations failure */ + if (!other.data || !other.psls) + { + if (other.data) + free(other.data); + if (other.psls) + free(other.psls); + return BH_OOM; + } + + /* Reset probe sequence lengths */ + memset(other.psls, 0, sizeof(size_t) * other.capacity); + + /* Copy data from old hashmap to the new hashmap */ + copy_hashmap(&other, hashmap); + } + + /* Swap hashmaps */ + bh_hashmap_destroy(hashmap); + *hashmap = other; + return BH_OK; +} + + +int bh_hashmap_insert(bh_hashmap_t *hashmap, + void *key, + void *value) +{ + size_t bucket, psl, tmp_psl; + bh_hashmap_node_t item, tmp; + + /* Try to stay below hashmap threshold */ + if (hashmap->size + 1 > hashmap->threshold) + if (bh_hashmap_reserve(hashmap, hashmap->size + 1)) + if (hashmap->size >= hashmap->capacity) + return BH_OOM; + + /* Prepare inserted data and set PSL to 1 */ + item.key = key; + item.value = value; + psl = 1; + + /* Calculate prefered bucket index */ + bucket = hashmap->hash(key) & (hashmap->capacity - 1); + + /* Find empty bucket */ + while (hashmap->psls[bucket]) + { + /* Current bucket is richer then us - swap elements */ + if (psl > hashmap->psls[bucket]) + { + tmp = hashmap->data[bucket]; + tmp_psl = hashmap->psls[bucket]; + hashmap->data[bucket] = item; + hashmap->psls[bucket] = psl; + item = tmp; + psl = tmp_psl; + } + + bucket = (bucket + 1) & (hashmap->capacity - 1); + psl++; + } + + /* Found empty bucket - place item here */ + hashmap->data[bucket] = item; + hashmap->psls[bucket] = psl; + hashmap->size++; + + return BH_OK; +} + + +void bh_hashmap_remove(bh_hashmap_t *hashmap, + void *key) +{ + void *iter; + + iter = bh_hashmap_iter_at(hashmap, key); + if (iter) + bh_hashmap_iter_remove(hashmap, iter); +} + + +int bh_hashmap_at(bh_hashmap_t *hashmap, + void *key, + void **value) +{ + void *iter; + iter = bh_hashmap_iter_at(hashmap, key); + + if (!iter) + return BH_NOTFOUND; + + if (value) + *value = bh_hashmap_iter_value(iter); + + return BH_OK; +} + + +int bh_hashmap_empty(bh_hashmap_t *hashmap) +{ + return !hashmap->size; +} + + +size_t bh_hashmap_size(bh_hashmap_t *hashmap) +{ + return hashmap->size; +} + + +size_t bh_hashmap_capacity(bh_hashmap_t *hashmap) +{ + return hashmap->capacity; +} + + +float bh_hashmap_factor(bh_hashmap_t *hashmap) +{ + return hashmap->factor; +} + + +void bh_hashmap_set_factor(bh_hashmap_t *hashmap, + float factor) +{ + /* Limit the factor value to [0.15, 1.0] */ + factor = (factor > 1.0f) ? (1.0f) : (factor); + factor = (factor < 0.15f) ? (0.15f) : (factor); + + /* Calculate new threshold value */ + hashmap->factor = factor; + hashmap->threshold = hashmap->capacity * factor; +} + + +void *bh_hashmap_iter_at(bh_hashmap_t *hashmap, + void *key) +{ + size_t bucket, psl; + + /* Nothing can be in empty map */ + if (!hashmap->size) + return NULL; + + /* Calculate prefered bucket index and set PSL to 1 */ + bucket = hashmap->hash(key) & (hashmap->capacity - 1); + psl = 1; + + /* Iterate hashmap until we find element or find richer bucket */ + while (hashmap->psls[bucket] >= psl && hashmap->equal(hashmap->data[bucket].key, key)) + { + bucket = (bucket + 1) & (hashmap->capacity - 1); + psl++; + } + + /* If bucket is poorer or equal to us - we found our element */ + if (hashmap->psls[bucket] >= psl) + return hashmap->data + bucket; + + return NULL; +} + + +void *bh_hashmap_iter_next(bh_hashmap_t *hashmap, + void *iter) +{ + bh_hashmap_node_t *item; + + item = (bh_hashmap_node_t *)iter; + while (1) + { + /* Advance or set iterator to the first element */ + if (item) + item++; + else + item = hashmap->data; + + /* Check iterator for validity */ + if (item >= hashmap->data + hashmap->capacity) + return NULL; + + /* Check iterator's item PSL */ + if (hashmap->psls[item - hashmap->data]) + return item; + } +} + + +void bh_hashmap_iter_remove(bh_hashmap_t *hashmap, + void *iter) +{ + size_t bucket, next_bucket; + + /* Check if hashmap is empty or we are given NULL iterator */ + if (!iter || !hashmap->size) + return; + + /* Adjust hashmap size, calculate current and next bucket index */ + hashmap->size--; + bucket = (bh_hashmap_node_t *)iter - hashmap->data; + next_bucket = (bucket + 1) & (hashmap->capacity - 1); + + /* Shift all elements toward their preffered place */ + while (hashmap->psls[next_bucket] > 1) + { + /* Copy item and adjust PSL */ + hashmap->data[bucket] = hashmap->data[next_bucket]; + hashmap->psls[bucket] = hashmap->psls[next_bucket] - 1; + + /* Calculate next bucket index */ + bucket = next_bucket; + next_bucket = (bucket + 1) & (hashmap->capacity - 1); + } + + /* Reset bucket's PSL (mark empty) */ + hashmap->psls[bucket] = 0; +} + + +void *bh_hashmap_iter_key(void *iter) +{ + return ((bh_hashmap_node_t *)iter)->key; +} + + +void *bh_hashmap_iter_value(void *iter) +{ + return ((bh_hashmap_node_t *)iter)->value; +} + |
