#include "cx/hash_map.h"
#include <string.h>
#include <assert.h>
#include <errno.h>
struct cx_hash_map_element_s {
struct cx_hash_map_element_s *next;
CxHashKey key;
char data[];
};
static void cx_hash_map_clear(
struct cx_map_s *map) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
for (
size_t i =
0; i < hash_map->bucket_count; i++) {
struct cx_hash_map_element_s *elem = hash_map->buckets[i];
if (elem !=
NULL) {
do {
struct cx_hash_map_element_s *next = elem->next;
cx_invoke_destructor(map, elem->data);
cxFree(map->collection.allocator, (
void *) elem->key.data);
cxFree(map->collection.allocator, elem);
elem = next;
}
while (elem !=
NULL);
hash_map->buckets[i] =
NULL;
}
}
map->collection.size =
0;
}
static void cx_hash_map_destructor(
struct cx_map_s *map) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
cx_hash_map_clear(map);
cxFree(map->collection.allocator, hash_map->buckets);
cxFree(map->collection.allocator, map);
}
static void *cx_hash_map_put(
CxMap *map,
CxHashKey key,
void *value
) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
const CxAllocator *allocator = map->collection.allocator;
uint64_t hash = key.hash;
if (hash ==
0) {
cx_hash_murmur(&key);
hash = key.hash;
}
size_t slot = hash % hash_map->bucket_count;
struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
struct cx_hash_map_element_s *prev =
NULL;
while (elm !=
NULL && elm->key.hash < hash) {
prev = elm;
elm = elm->next;
}
if (elm !=
NULL && cx_hash_key_cmp(&elm->key, &key) ==
0) {
cx_invoke_destructor(map, elm->data);
if (value ==
NULL) {
memset(elm->data,
0, map->collection.elem_size);
}
else if (map->collection.store_pointer) {
memcpy(elm->data, &value,
sizeof(
void *));
}
else {
memcpy(elm->data, value, map->collection.elem_size);
}
}
else {
struct cx_hash_map_element_s *e = cxMalloc(
allocator,
sizeof(
struct cx_hash_map_element_s) + map->collection.elem_size
);
if (e ==
NULL)
return NULL;
if (value ==
NULL) {
memset(e->data,
0, map->collection.elem_size);
}
else if (map->collection.store_pointer) {
memcpy(e->data, &value,
sizeof(
void *));
}
else {
memcpy(e->data, value, map->collection.elem_size);
}
void *kd = cxMalloc(allocator, key.len);
if (kd ==
NULL) {
cxFree(allocator, e);
return NULL;
}
memcpy(kd, key.data, key.len);
e->key.data = kd;
e->key.len = key.len;
e->key.hash = hash;
if (prev ==
NULL) {
hash_map->buckets[slot] = e;
}
else {
prev->next = e;
}
e->next = elm;
elm = e;
map->collection.size++;
}
return elm->data;
}
static void cx_hash_map_unlink(
struct cx_hash_map_s *hash_map,
size_t slot,
struct cx_hash_map_element_s *prev,
struct cx_hash_map_element_s *elm
) {
if (prev ==
NULL) {
hash_map->buckets[slot] = elm->next;
}
else {
prev->next = elm->next;
}
cxFree(hash_map->base.collection.allocator, (
void *) elm->key.data);
cxFree(hash_map->base.collection.allocator, elm);
hash_map->base.collection.size--;
}
static int cx_hash_map_get_remove(
CxMap *map,
CxHashKey key,
void *targetbuf,
bool remove
) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
uint64_t hash = key.hash;
if (hash ==
0) {
cx_hash_murmur(&key);
hash = key.hash;
}
size_t slot = hash % hash_map->bucket_count;
struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
struct cx_hash_map_element_s *prev =
NULL;
while (elm && elm->key.hash <= hash) {
if (cx_hash_key_cmp(&elm->key, &key) ==
0) {
if (remove) {
if (targetbuf ==
NULL) {
cx_invoke_destructor(map, elm->data);
}
else {
memcpy(targetbuf, elm->data, map->collection.elem_size);
}
cx_hash_map_unlink(hash_map, slot, prev, elm);
}
else {
assert(targetbuf !=
NULL);
void *data =
NULL;
if (map->collection.store_pointer) {
data = *(
void **) elm->data;
}
else {
data = elm->data;
}
memcpy(targetbuf, &data,
sizeof(
void *));
}
return 0;
}
prev = elm;
elm = prev->next;
}
return 1;
}
static void *cx_hash_map_get(
const CxMap *map,
CxHashKey key
) {
void *ptr =
NULL;
int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false);
return found ==
0 ? ptr :
NULL;
}
static int cx_hash_map_remove(
CxMap *map,
CxHashKey key,
void *targetbuf
) {
return cx_hash_map_get_remove(map, key, targetbuf, true);
}
static void *cx_hash_map_iter_current_entry(
const void *it) {
const CxMapIterator *iter = it;
return (
void*) &iter->entry;
}
static void *cx_hash_map_iter_current_key(
const void *it) {
const CxMapIterator *iter = it;
return (
void*) iter->entry.key;
}
static void *cx_hash_map_iter_current_value(
const void *it) {
const CxMapIterator *iter = it;
return iter->entry.value;
}
static bool cx_hash_map_iter_valid(
const void *it) {
const CxMapIterator *iter = it;
return iter->elem !=
NULL;
}
static void cx_hash_map_iter_next(
void *it) {
CxMapIterator *iter = it;
CxMap *map = iter->map;
struct cx_hash_map_s *hmap = (
struct cx_hash_map_s *) map;
struct cx_hash_map_element_s *elm = iter->elem;
if (iter->base.remove) {
iter->base.remove = false;
struct cx_hash_map_element_s *next = elm->next;
struct cx_hash_map_element_s *prev =
NULL;
if (hmap->buckets[iter->slot] != elm) {
prev = hmap->buckets[iter->slot];
while (prev->next != elm) {
prev = prev->next;
}
}
cx_invoke_destructor(map, elm->data);
cx_hash_map_unlink(hmap, iter->slot, prev, elm);
iter->elem_count--;
elm = next;
}
else {
elm = elm->next;
iter->index++;
}
while (elm ==
NULL && ++iter->slot < hmap->bucket_count) {
elm = hmap->buckets[iter->slot];
}
iter->elem = elm;
if (elm !=
NULL) {
iter->entry.key = &elm->key;
if (map->collection.store_pointer) {
iter->entry.value = *(
void **) elm->data;
}
else {
iter->entry.value = elm->data;
}
}
}
static CxMapIterator cx_hash_map_iterator(
const CxMap *map,
enum cx_map_iterator_type type
) {
CxMapIterator iter;
iter.map = (CxMap*) map;
iter.elem_count = map->collection.size;
switch (type) {
case CX_MAP_ITERATOR_PAIRS:
iter.elem_size =
sizeof(CxMapEntry);
iter.base.current = cx_hash_map_iter_current_entry;
break;
case CX_MAP_ITERATOR_KEYS:
iter.elem_size =
sizeof(CxHashKey);
iter.base.current = cx_hash_map_iter_current_key;
break;
case CX_MAP_ITERATOR_VALUES:
iter.elem_size = map->collection.elem_size;
iter.base.current = cx_hash_map_iter_current_value;
break;
default:
assert(false);
}
iter.base.valid = cx_hash_map_iter_valid;
iter.base.next = cx_hash_map_iter_next;
iter.base.remove = false;
iter.base.allow_remove = true;
iter.slot =
0;
iter.index =
0;
if (map->collection.size >
0) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
struct cx_hash_map_element_s *elm = hash_map->buckets[
0];
while (elm ==
NULL) {
elm = hash_map->buckets[++iter.slot];
}
iter.elem = elm;
iter.entry.key = &elm->key;
if (map->collection.store_pointer) {
iter.entry.value = *(
void **) elm->data;
}
else {
iter.entry.value = elm->data;
}
}
else {
iter.elem =
NULL;
}
return iter;
}
static cx_map_class cx_hash_map_class = {
cx_hash_map_destructor,
cx_hash_map_clear,
cx_hash_map_put,
cx_hash_map_get,
cx_hash_map_remove,
cx_hash_map_iterator,
};
CxMap *cxHashMapCreate(
const CxAllocator *allocator,
size_t itemsize,
size_t buckets
) {
if (allocator ==
NULL) {
allocator = cxDefaultAllocator;
}
if (buckets ==
0) {
buckets =
16;
}
struct cx_hash_map_s *map = cxCalloc(allocator,
1,
sizeof(
struct cx_hash_map_s));
if (map ==
NULL)
return NULL;
map->bucket_count = buckets;
map->buckets = cxCalloc(allocator, buckets,
sizeof(
struct cx_hash_map_element_s *));
if (map->buckets ==
NULL) {
cxFree(allocator, map);
return NULL;
}
map->base.cl = &cx_hash_map_class;
map->base.collection.allocator = allocator;
if (itemsize >
0) {
map->base.collection.elem_size = itemsize;
}
else {
map->base.collection.elem_size =
sizeof(
void *);
map->base.collection.store_pointer = true;
}
return (CxMap *) map;
}
int cxMapRehash(CxMap *map) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
if (map->collection.size > ((hash_map->bucket_count *
3) >>
2)) {
size_t new_bucket_count = (map->collection.size *
5) >>
1;
if (new_bucket_count < hash_map->bucket_count) {
errno =
EOVERFLOW;
return 1;
}
struct cx_hash_map_element_s **new_buckets = cxCalloc(
map->collection.allocator,
new_bucket_count,
sizeof(
struct cx_hash_map_element_s *)
);
if (new_buckets ==
NULL)
return 1;
for (
size_t slot =
0; slot < hash_map->bucket_count; slot++) {
struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
while (elm !=
NULL) {
struct cx_hash_map_element_s *next = elm->next;
size_t new_slot = elm->key.hash % new_bucket_count;
struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
struct cx_hash_map_element_s *bucket_prev =
NULL;
while (bucket_next !=
NULL &&
bucket_next->key.hash < elm->key.hash) {
bucket_prev = bucket_next;
bucket_next = bucket_next->next;
}
if (bucket_prev ==
NULL) {
elm->next = new_buckets[new_slot];
new_buckets[new_slot] = elm;
}
else {
bucket_prev->next = elm;
elm->next = bucket_next;
}
elm = next;
}
}
hash_map->bucket_count = new_bucket_count;
cxFree(map->collection.allocator, hash_map->buckets);
hash_map->buckets = new_buckets;
}
return 0;
}