2025-02-02 21:40:31 +03:00
|
|
|
#include <BH/Hashmap.h>
|
2025-01-18 17:24:36 +03:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
typedef struct BH_HashmapNode
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
void *key;
|
|
|
|
|
void *value;
|
2025-01-30 13:53:26 +03:00
|
|
|
} BH_HashmapNode;
|
2025-01-18 17:24:36 +03:00
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
struct BH_Hashmap
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapNode *data;
|
2025-01-18 17:24:36 +03:00
|
|
|
size_t *psls;
|
|
|
|
|
size_t size;
|
|
|
|
|
size_t capacity;
|
|
|
|
|
size_t threshold;
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_EqualCallback equal;
|
|
|
|
|
BH_HashCallback hash;
|
2025-01-18 17:24:36 +03:00
|
|
|
float factor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
static void BH_HashmapInit(BH_Hashmap *hashmap,
|
|
|
|
|
BH_EqualCallback equal,
|
|
|
|
|
BH_HashCallback hash)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
memset(hashmap, 0, sizeof(*hashmap));
|
|
|
|
|
hashmap->factor = 0.75f;
|
|
|
|
|
hashmap->equal = equal;
|
|
|
|
|
hashmap->hash = hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
static void BH_HashmapDestroy(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
if (hashmap->capacity)
|
|
|
|
|
{
|
|
|
|
|
free(hashmap->data);
|
|
|
|
|
free(hashmap->psls);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
static int BH_CalcCapacity(size_t size,
|
|
|
|
|
float factor,
|
|
|
|
|
size_t *capacity,
|
|
|
|
|
size_t *threshold)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
/* 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 */
|
2025-01-30 13:53:26 +03:00
|
|
|
if (*capacity >= ((size_t)-1) / sizeof(BH_HashmapNode))
|
2025-01-18 17:24:36 +03:00
|
|
|
return BH_OOM;
|
|
|
|
|
|
|
|
|
|
return BH_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
static void BH_CopyHashmap(BH_Hashmap *dest,
|
|
|
|
|
BH_Hashmap *src)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
void *iter;
|
|
|
|
|
|
|
|
|
|
/* Iterate and insert data into hashmap */
|
2025-01-30 13:53:26 +03:00
|
|
|
iter = BH_HashmapIterNext(src, NULL);
|
2025-01-18 17:24:36 +03:00
|
|
|
while (iter)
|
|
|
|
|
{
|
|
|
|
|
void *key, *value;
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
key = BH_HashmapIterKey(iter);
|
|
|
|
|
value = BH_HashmapIterValue(iter);
|
|
|
|
|
BH_HashmapInsert(dest, key, value);
|
2025-01-18 17:24:36 +03:00
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
iter = BH_HashmapIterNext(src, iter);
|
2025-01-18 17:24:36 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_Hashmap *BH_HashmapNew(BH_EqualCallback equal,
|
|
|
|
|
BH_HashCallback hash)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_Hashmap *result;
|
2025-01-18 17:24:36 +03:00
|
|
|
|
|
|
|
|
result = malloc(sizeof(*result));
|
|
|
|
|
if (result)
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapInit(result, equal, hash);
|
2025-01-18 17:24:36 +03:00
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void BH_HashmapFree(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapDestroy(hashmap);
|
2025-01-18 17:24:36 +03:00
|
|
|
free(hashmap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void BH_HashmapClear(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
if (hashmap->capacity)
|
|
|
|
|
memset(hashmap->psls, 0, hashmap->capacity * sizeof(size_t));
|
|
|
|
|
hashmap->size = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
int BH_HashmapReserve(BH_Hashmap *hashmap,
|
|
|
|
|
size_t size)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_Hashmap other;
|
2025-01-18 17:24:36 +03:00
|
|
|
size_t capacity, threshold;
|
|
|
|
|
|
|
|
|
|
/* New capacity can't be smaller then current hashmap size */
|
|
|
|
|
if (size < hashmap->size)
|
|
|
|
|
size = hashmap->size;
|
|
|
|
|
|
|
|
|
|
/* Calculate new capacity */
|
2025-01-30 13:53:26 +03:00
|
|
|
if (BH_CalcCapacity(size, hashmap->factor, &capacity, &threshold))
|
2025-01-18 17:24:36 +03:00
|
|
|
return BH_OOM;
|
|
|
|
|
|
|
|
|
|
/* Prevent same size reallocation */
|
|
|
|
|
if (capacity == hashmap->capacity)
|
|
|
|
|
return BH_OK;
|
|
|
|
|
|
|
|
|
|
/* Initialize new hashmap */
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapInit(&other, hashmap->equal, hashmap->hash);
|
2025-01-18 17:24:36 +03:00
|
|
|
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 */
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_CopyHashmap(&other, hashmap);
|
2025-01-18 17:24:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Swap hashmaps */
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapDestroy(hashmap);
|
2025-01-18 17:24:36 +03:00
|
|
|
*hashmap = other;
|
|
|
|
|
return BH_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
int BH_HashmapInsert(BH_Hashmap *hashmap,
|
|
|
|
|
void *key,
|
|
|
|
|
void *value)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
size_t bucket, psl, tmp_psl;
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapNode item, tmp;
|
2025-01-18 17:24:36 +03:00
|
|
|
|
|
|
|
|
/* Try to stay below hashmap threshold */
|
|
|
|
|
if (hashmap->size + 1 > hashmap->threshold)
|
2025-01-30 13:53:26 +03:00
|
|
|
if (BH_HashmapReserve(hashmap, hashmap->size + 1))
|
2025-01-18 17:24:36 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void BH_HashmapRemove(BH_Hashmap *hashmap,
|
|
|
|
|
void *key)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
void *iter;
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
iter = BH_HashmapIterAt(hashmap, key);
|
2025-01-18 17:24:36 +03:00
|
|
|
if (iter)
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapIterRemove(hashmap, iter);
|
2025-01-18 17:24:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
int BH_HashmapAt(BH_Hashmap *hashmap,
|
|
|
|
|
void *key,
|
|
|
|
|
void **value)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
void *iter;
|
2025-01-30 13:53:26 +03:00
|
|
|
iter = BH_HashmapIterAt(hashmap, key);
|
2025-01-18 17:24:36 +03:00
|
|
|
|
|
|
|
|
if (!iter)
|
|
|
|
|
return BH_NOTFOUND;
|
|
|
|
|
|
|
|
|
|
if (value)
|
2025-01-30 13:53:26 +03:00
|
|
|
*value = BH_HashmapIterValue(iter);
|
2025-01-18 17:24:36 +03:00
|
|
|
|
|
|
|
|
return BH_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
int BH_HashmapEmpty(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
return !hashmap->size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
size_t BH_HashmapSize(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
return hashmap->size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
size_t BH_HashmapCapacity(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
return hashmap->capacity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
float BH_HashmapFactor(BH_Hashmap *hashmap)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
return hashmap->factor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void BH_HashmapSetFactor(BH_Hashmap *hashmap,
|
|
|
|
|
float factor)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void *BH_HashmapIterAt(BH_Hashmap *hashmap,
|
|
|
|
|
void *key)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void *BH_HashmapIterNext(BH_Hashmap *hashmap,
|
|
|
|
|
void *iter)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
BH_HashmapNode *item;
|
2025-01-18 17:24:36 +03:00
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
item = (BH_HashmapNode *)iter;
|
2025-01-18 17:24:36 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void BH_HashmapIterRemove(BH_Hashmap *hashmap,
|
|
|
|
|
void *iter)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
|
|
|
|
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--;
|
2025-01-30 13:53:26 +03:00
|
|
|
bucket = (BH_HashmapNode *)iter - hashmap->data;
|
2025-01-18 17:24:36 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void *BH_HashmapIterKey(void *iter)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
return ((BH_HashmapNode *)iter)->key;
|
2025-01-18 17:24:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-30 13:53:26 +03:00
|
|
|
void *BH_HashmapIterValue(void *iter)
|
2025-01-18 17:24:36 +03:00
|
|
|
{
|
2025-01-30 13:53:26 +03:00
|
|
|
return ((BH_HashmapNode *)iter)->value;
|
2025-01-18 17:24:36 +03:00
|
|
|
}
|