#include "cx/hash_map.h"
#include "cx/utils.h"
#include <string.h>
#include <assert.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;
cx_for_n(i, hash_map->bucket_count) {
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 int 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;
unsigned 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 && elm->key.hash == hash && elm->key.len == key.len &&
memcmp(elm->key.data, key.data, key.len) ==
0) {
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 -
1;
}
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) {
return -
1;
}
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;
map->collection.size++;
}
return 0;
}
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 void *cx_hash_map_get_remove(
CxMap *map,
CxHashKey key,
bool remove,
bool destroy
) {
struct cx_hash_map_s *hash_map = (
struct cx_hash_map_s *) map;
unsigned 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 (elm->key.hash == hash && elm->key.len == key.len) {
if (memcmp(elm->key.data, key.data, key.len) ==
0) {
void *data =
NULL;
if (destroy) {
cx_invoke_destructor(map, elm->data);
}
else {
if (map->collection.store_pointer) {
data = *(
void **) elm->data;
}
else {
data = elm->data;
}
}
if (remove) {
cx_hash_map_unlink(hash_map, slot, prev, elm);
}
return data;
}
}
prev = elm;
elm = prev->next;
}
return NULL;
}
static void *cx_hash_map_get(
const CxMap *map,
CxHashKey key
) {
return cx_hash_map_get_remove((CxMap *) map, key, false, false);
}
static void *cx_hash_map_remove(
CxMap *map,
CxHashKey key,
bool destroy
) {
return cx_hash_map_get_remove(map, key, true, destroy);
}
static void *cx_hash_map_iter_current_entry(
const void *it) {
const struct cx_iterator_s *iter = it;
return (
struct cx_map_entry_s *) &(iter->kv_data);
}
static void *cx_hash_map_iter_current_key(
const void *it) {
const struct cx_iterator_s *iter = it;
struct cx_hash_map_element_s *elm = iter->elem_handle;
return &elm->key;
}
static void *cx_hash_map_iter_current_value(
const void *it) {
const struct cx_iterator_s *iter = it;
const struct cx_hash_map_s *map = iter->src_handle.c;
struct cx_hash_map_element_s *elm = iter->elem_handle;
if (map->base.collection.store_pointer) {
return *(
void **) elm->data;
}
else {
return elm->data;
}
}
static bool cx_hash_map_iter_valid(
const void *it) {
const struct cx_iterator_s *iter = it;
return iter->elem_handle !=
NULL;
}
static void cx_hash_map_iter_next(
void *it) {
struct cx_iterator_s *iter = it;
struct cx_hash_map_element_s *elm = iter->elem_handle;
struct cx_hash_map_s *map = iter->src_handle.m;
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 (map->buckets[iter->slot] != elm) {
prev = map->buckets[iter->slot];
while (prev->next != elm) {
prev = prev->next;
}
}
cx_invoke_destructor((
struct cx_map_s *) map, elm->data);
cx_hash_map_unlink(map, iter->slot, prev, elm);
elm = next;
}
else {
elm = elm->next;
iter->index++;
}
while (elm ==
NULL && ++iter->slot < map->bucket_count) {
elm = map->buckets[iter->slot];
}
iter->elem_handle = elm;
if (elm ==
NULL) {
iter->kv_data.key =
NULL;
iter->kv_data.value =
NULL;
}
else {
iter->kv_data.key = &elm->key;
if (map->base.collection.store_pointer) {
iter->kv_data.value = *(
void **) elm->data;
}
else {
iter->kv_data.value = elm->data;
}
}
}
static CxIterator cx_hash_map_iterator(
const CxMap *map,
enum cx_map_iterator_type type
) {
CxIterator iter;
iter.src_handle.c = 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.mutating = false;
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_handle = elm;
iter.kv_data.key = &elm->key;
if (map->collection.store_pointer) {
iter.kv_data.value = *(
void **) elm->data;
}
else {
iter.kv_data.value = elm->data;
}
}
else {
iter.elem_handle =
NULL;
iter.kv_data.key =
NULL;
iter.kv_data.value =
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 (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.store_pointer = false;
map->base.collection.elem_size = itemsize;
}
else {
map->base.collection.store_pointer = true;
map->base.collection.elem_size =
sizeof(
void *);
}
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;
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;
}
cx_for_n(slot, hash_map->bucket_count) {
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;
}