blob: bb5f2466c627bbbccb7fae1f4d494d5f854133f0 [file] [log] [blame] [raw]
#ifndef H_FIO_SIMPLE_HASH_H
/*
Copyright: Boaz Segev, 2017-2018
License: MIT
*/
/**
* A simple ordered Hash Table implementation, with a minimal API.
*
* Keys types are adjustable using macros. A single C file is limited to a
* single key type. Keys can be strings, integers, anything. By default, keys
* are uint64_t.
*
* Unique keys are required. Full key collisions aren't handled, instead the old
* value is replaced and returned.
*
* Partial key collisions are handled by seeking forward and attempting to find
* a close enough spot. If a close enough spot isn't found, rehashing is
* initiated and memory consumption increases.
*
* The Hash Table is ordered using an internal ordered array of data containers
* with duplicates of the key data (to improve cache locality).
*
* The file was written to be compatible with C++ as well as C, hence some
* pointer casting.
*/
#define H_FIO_SIMPLE_HASH_H
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef FIO_FUNC
#define FIO_FUNC static __attribute__((unused))
#endif
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* extra collision protection can be obtained by defining ALL of the following:
* * FIO_HASH_KEY_TYPE - the type used for keys.
* * FIO_HASH_KEY_INVALID - an invalid key with it's bytes set to zero.
* * FIO_HASH_KEY2UINT(key) - converts a key to a hash value number.
* * FIO_HASH_COMPARE_KEYS(k1, k2) - compares two key.
* * FIO_HASH_KEY_ISINVALID(key) - tests for an invalid key.
* * FIO_HASH_KEY_COPY(key) - creates a persistent copy of the key.
* * FIO_HASH_KEY_DESTROY(key) - destroys (or frees) the key's copy.
*
* Note: FIO_HASH_COMPARE_KEYS will be used to compare against
* FIO_HASH_KEY_INVALID as well as valid keys.
*
* Note: Before freeing the Hash, FIO_HASH_KEY_DESTROY should be called for
* every key. This is NOT automatic. see the FIO_HASH_FOR_EMPTY(h) macro.
*/
#if !defined(FIO_HASH_COMPARE_KEYS) || !defined(FIO_HASH_KEY_TYPE) || \
!defined(FIO_HASH_KEY2UINT) || !defined(FIO_HASH_KEY_INVALID) || \
!defined(FIO_HASH_KEY_ISINVALID) || !defined(FIO_HASH_KEY_COPY) || \
!defined(FIO_HASH_KEY_DESTROY)
#define FIO_HASH_KEY_TYPE uint64_t
#define FIO_HASH_KEY_INVALID 0
#define FIO_HASH_KEY2UINT(key) (key)
#define FIO_HASH_COMPARE_KEYS(k1, k2) ((k1) == (k2))
#define FIO_HASH_KEY_ISINVALID(key) ((key) == 0)
#define FIO_HASH_KEY_COPY(key) (key)
#define FIO_HASH_KEY_DESTROY(key) ((void)0)
#elif !defined(FIO_HASH_NO_TEST)
#define FIO_HASH_NO_TEST 1
#endif
#ifndef FIO_HASH_INITIAL_CAPACITY
/* MUST be a power of 2 */
#define FIO_HASH_INITIAL_CAPACITY 4
#endif
#ifndef FIO_HASH_MAX_MAP_SEEK
/* MUST be a power of 2 */
#define FIO_HASH_MAX_MAP_SEEK (256)
#endif
#ifndef FIO_HASH_REALLOC /* NULL ptr indicates new allocation */
#define FIO_HASH_REALLOC(ptr, original_size, new_size, valid_data_length) \
realloc((ptr), (new_size))
#endif
#ifndef FIO_HASH_CALLOC
#define FIO_HASH_CALLOC(size, count) calloc((size), (count))
#endif
#ifndef FIO_HASH_FREE
#define FIO_HASH_FREE(ptr, size) free((ptr))
#endif
/* *****************************************************************************
Hash API
***************************************************************************** */
/** The Hash Table container type. */
typedef struct fio_hash_s fio_hash_s;
/** Allocates and initializes internal data and resources. */
FIO_FUNC void fio_hash_new(fio_hash_s *hash);
/** Allocates and initializes internal data and resources with the requested
* capacity. */
FIO_FUNC void fio_hash_new2(fio_hash_s *hash, size_t capa);
/** Deallocates any internal resources. */
FIO_FUNC void fio_hash_free(fio_hash_s *hash);
/** Locates an object in the Hash Map Table according to the hash key value. */
FIO_FUNC inline void *fio_hash_find(fio_hash_s *hash, FIO_HASH_KEY_TYPE key);
/**
* Inserts an object to the Hash Map Table, rehashing if required, returning the
* old object if it exists.
*
* Set obj to NULL to remove an existing data (the existing object will be
* returned).
*/
FIO_FUNC void *fio_hash_insert(fio_hash_s *hash, FIO_HASH_KEY_TYPE key,
void *obj);
/**
* Allows the Hash to be momenterally used as a stack, poping the last element
* entered.
*
* If a pointer to `key` is provided, the element's key will be placed in it's
* place.
*
* Remember that keys are likely to be freed as well (`FIO_HASH_KEY_DESTROY`).
*/
FIO_FUNC void *fio_hash_pop(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key);
/**
* Allows a peak at the Hash's last element.
*
* If a pointer to `key` is provided, the element's key will be placed in it's
* place.
*
* Remember that keys might be destroyed if the Hash is altered
* (`FIO_HASH_KEY_DESTROY`).
*/
FIO_FUNC void *fio_hash_last(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key);
/** Returns the number of elements currently in the Hash Table. */
FIO_FUNC inline size_t fio_hash_count(const fio_hash_s *hash);
/**
* Returns a temporary theoretical Hash map capacity.
* This could be used for testing performance and memory consumption.
*/
FIO_FUNC inline size_t fio_hash_capa(const fio_hash_s *hash);
/**
* Returns non-zero if the hash is fragmented (more than 50% holes).
*/
FIO_FUNC inline size_t fio_hash_is_fragmented(const fio_hash_s *hash);
/**
* Attempts to minimize memory usage by removing empty spaces caused by deleted
* items and rehashing the Hash Map.
*
* Returns the updated hash map capacity.
*/
FIO_FUNC inline size_t fio_hash_compact(fio_hash_s *hash);
/** Forces a rehashing of the hash. */
FIO_FUNC void fio_hash_rehash(fio_hash_s *hash);
/**
* Iteration using a callback for each entry in the Hash Table.
*
* The callback task function must accept the hash key, the entry data and an
* opaque user pointer:
*
* int example_task(FIO_HASH_KEY_TYPE key, void *obj, void *arg) {return 0;}
*
* If the callback returns -1, the loop is broken. Any other value is ignored.
*
* Returns the relative "stop" position, i.e., the number of items processed +
* the starting point.
*/
FIO_FUNC inline size_t fio_hash_each(fio_hash_s *hash, const size_t start_at,
int (*task)(FIO_HASH_KEY_TYPE key,
void *obj, void *arg),
void *arg);
/**
* A macro for a `for` loop that iterates over all the hashed objects (in
* order).
*
* `hash` a pointer to the hash table variable and `i` is a temporary variable
* name to be created for iteration.
*
* `i->key` is the key and `i->obj` is the hashed data.
*/
#define FIO_HASH_FOR_LOOP(hash, i)
/**
* A macro for a `for` loop that will iterate over all the hashed objects (in
* order) and empties the hash, later calling `fio_hash_free` to free the hash
* (but not the container).
*
* `hash` a pointer to the hash table variable and `i` is a temporary variable
* name to be created for iteration.
*
* `i->key` is the key and `i->obj` is the hashed data.
*
* Free the objects and the Hash Map container manually (if required). Custom
* keys will be freed automatically when using this macro.
*
*/
#define FIO_HASH_FOR_FREE(hash, i)
/**
* A macro for a `for` loop that iterates over all the hashed objects (in
* order) and empties the hash.
*
* This will also reallocate the map's memory (to zero out the data), so if this
* is performed before calling `fio_hash_free`, use FIO_HASH_FOR_FREE instead.
*
* `hash` a pointer to the hash table variable and `i` is a temporary variable
* name to be created for iteration.
*
* `i->key` is the key and `i->obj` is the hashed data.
*
* Free the objects and the Hash Map container manually (if required). Custom
* keys will be freed automatically when using this macro.
*
*/
#define FIO_HASH_FOR_EMPTY(hash, i)
/* *****************************************************************************
Hash Table Internal Data Structures
***************************************************************************** */
typedef struct fio_hash_data_ordered_s {
FIO_HASH_KEY_TYPE key; /* another copy for memory cache locality */
void *obj;
} fio_hash_data_ordered_s;
typedef struct fio_hash_data_s {
FIO_HASH_KEY_TYPE key; /* another copy for memory cache locality */
struct fio_hash_data_ordered_s *obj;
} fio_hash_data_s;
/* the information in the Hash Map structure should be considered READ ONLY. */
struct fio_hash_s {
uintptr_t count;
uintptr_t capa;
uintptr_t pos;
uintptr_t mask;
fio_hash_data_ordered_s *ordered;
fio_hash_data_s *map;
};
#undef FIO_HASH_FOR_LOOP
#define FIO_HASH_FOR_LOOP(hash, container) \
for (fio_hash_data_ordered_s *container = (hash)->ordered; \
container && (container < (hash)->ordered + (hash)->pos); ++container)
#undef FIO_HASH_FOR_FREE
#define FIO_HASH_FOR_FREE(hash, container) \
for (fio_hash_data_ordered_s *container = (hash)->ordered; \
(container && container >= (hash)->ordered && \
(container < (hash)->ordered + (hash)->pos)) || \
((fio_hash_free(hash), (hash)->ordered) != NULL); \
FIO_HASH_KEY_DESTROY(container->key), (++container))
#undef FIO_HASH_FOR_EMPTY
#define FIO_HASH_FOR_EMPTY(hash, container) \
for (fio_hash_data_ordered_s *container = (hash)->ordered; \
(container && (container < (hash)->ordered + (hash)->pos)) || \
(memset((hash)->map, 0, (hash)->capa * sizeof(*(hash)->map)), \
((hash)->pos = (hash)->count = 0)); \
(FIO_HASH_KEY_DESTROY(container->key), \
container->key = FIO_HASH_KEY_INVALID, container->obj = NULL), \
(++container))
#define FIO_HASH_INIT \
{ .capa = 0 }
/* *****************************************************************************
Hash allocation / deallocation.
***************************************************************************** */
/** Allocates and initializes internal data and resources with the requested
* capacity. */
FIO_FUNC void fio_hash__new__internal__safe_capa(fio_hash_s *h, size_t capa) {
*h = (fio_hash_s){
.mask = (capa - 1),
.map = (fio_hash_data_s *)FIO_HASH_CALLOC(sizeof(*h->map), capa),
.ordered =
(fio_hash_data_ordered_s *)FIO_HASH_CALLOC(sizeof(*h->ordered), capa),
.capa = capa,
};
if (!h->map || !h->ordered) {
perror("ERROR: Hash Table couldn't allocate memory");
exit(errno);
}
h->ordered[0] =
(fio_hash_data_ordered_s){.key = FIO_HASH_KEY_INVALID, .obj = NULL};
}
/** Allocates and initializes internal data and resources with the requested
* capacity. */
FIO_FUNC void fio_hash_new2(fio_hash_s *h, size_t capa) {
size_t act_capa = 1;
while (act_capa < capa)
act_capa = act_capa << 1;
fio_hash__new__internal__safe_capa(h, act_capa);
}
FIO_FUNC void fio_hash_new(fio_hash_s *h) {
fio_hash__new__internal__safe_capa(h, FIO_HASH_INITIAL_CAPACITY);
}
FIO_FUNC void fio_hash_free(fio_hash_s *h) {
FIO_HASH_FREE(h->map, h->capa);
FIO_HASH_FREE(h->ordered, h->capa);
*h = (fio_hash_s){.map = NULL};
}
/* *****************************************************************************
Internal HashMap Functions
***************************************************************************** */
FIO_FUNC inline uintptr_t fio_hash_map_cuckoo_steps(uintptr_t step) {
return (step * 3);
}
/* seeks the hash's position in the map */
FIO_FUNC fio_hash_data_s *fio_hash_seek_pos_(fio_hash_s *hash,
FIO_HASH_KEY_TYPE key) {
const uint64_t hashed_key = FIO_HASH_KEY2UINT(key);
/* TODO: consider implementing Robing Hood reordering during seek? */
fio_hash_data_s *pos = hash->map + (hashed_key & hash->mask);
uintptr_t i = 0;
const uintptr_t limit = hash->capa > FIO_HASH_MAX_MAP_SEEK
? FIO_HASH_MAX_MAP_SEEK
: ((hash->capa >> 1) | 1);
while (i < limit) {
if (FIO_HASH_KEY_ISINVALID(pos->key) ||
(FIO_HASH_KEY2UINT(pos->key) == hashed_key &&
FIO_HASH_COMPARE_KEYS(pos->key, key)))
return pos;
pos = hash->map +
(((hashed_key & hash->mask) + fio_hash_map_cuckoo_steps(i++)) &
hash->mask);
}
return NULL;
}
/* finds an object in the map */
FIO_FUNC inline void *fio_hash_find(fio_hash_s *hash, FIO_HASH_KEY_TYPE key) {
if (!hash->map)
return NULL;
fio_hash_data_s *info = fio_hash_seek_pos_(hash, key);
if (!info || !info->obj)
return NULL;
return (void *)info->obj->obj;
}
/* inserts an object to the map, rehashing if required, returning old object.
* set obj to NULL to remove existing data.
*/
FIO_FUNC void *fio_hash_insert(fio_hash_s *hash, FIO_HASH_KEY_TYPE key,
void *obj) {
/* nothing to do if there's nothing to do. */
if (!obj && !hash->count) {
return NULL;
}
/* ensure some space */
if (obj && hash->pos >= hash->capa) {
fio_hash_rehash(hash);
}
/* find where the object belongs in the map */
fio_hash_data_s *info = fio_hash_seek_pos_(hash, key);
if (!info && !obj)
return NULL;
while (!info) {
fio_hash_rehash(hash);
info = fio_hash_seek_pos_(hash, key);
}
if (!info->obj) {
/* a fresh object */
if (obj == NULL) {
/* nothing to delete */
return NULL;
}
/* add object to ordered hash */
hash->ordered[hash->pos] =
(fio_hash_data_ordered_s){.key = FIO_HASH_KEY_COPY(key), .obj = obj};
/* add object to map */
*info = (fio_hash_data_s){.key = hash->ordered[hash->pos].key,
.obj = hash->ordered + hash->pos};
/* manage counters and mark end position */
hash->count++;
hash->pos++;
return NULL;
}
if (!obj && !info->obj->obj) {
/* a delete operation for an empty element */
return NULL;
}
/* an object exists, this is a "replace/delete" operation */
const void *old = (void *)info->obj->obj;
if (!obj) {
/* it was a delete operation */
if (info->obj == hash->ordered + hash->pos - 1) {
/* we removed the last ordered element, no need to keep any holes. */
--hash->pos;
FIO_HASH_KEY_DESTROY(hash->ordered[hash->pos].key);
hash->ordered[hash->pos] =
(fio_hash_data_ordered_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
*info = (fio_hash_data_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
if (hash->pos && !hash->ordered[hash->pos - 1].obj) {
fio_hash_pop(hash, NULL);
} else {
--hash->count;
}
return (void *)old;
}
--hash->count;
} else if (!old) {
/* inserted an item after a previous one was removed. */
++hash->count;
}
info->obj->obj = obj;
return (void *)old;
}
/**
* Allows the Hash to be momenterally used as a stack, poping the last element
* entered.
* Remember that keys might have to be freed as well (`FIO_HASH_KEY_DESTROY`).
*/
FIO_FUNC void *fio_hash_pop(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key) {
if (!hash->pos)
return NULL;
--(hash->pos);
--(hash->count);
void *old = hash->ordered[hash->pos].obj;
/* removing hole from hashtable is possible because it's the last element */
fio_hash_data_s *info =
fio_hash_seek_pos_(hash, hash->ordered[hash->pos].key);
if (!info) {
/* no info is a data corruption error. */
fprintf(stderr, "FATAL ERROR: (fio_hash) unexpected missing container.\n");
exit(-1);
}
*info = (fio_hash_data_s){.obj = NULL};
/* cleanup key (or copy to target) and reset the ordered position. */
if (key)
*key = hash->ordered[hash->pos].key;
else
FIO_HASH_KEY_DESTROY(hash->ordered[hash->pos].key);
hash->ordered[hash->pos] =
(fio_hash_data_ordered_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
/* remove any holes from the top (top is kept tight) */
while (hash->pos && hash->ordered[hash->pos - 1].obj == NULL) {
--(hash->pos);
info = fio_hash_seek_pos_(hash, hash->ordered[hash->pos].key);
if (!info) {
/* no info is a data corruption error. */
fprintf(stderr,
"FATAL ERROR: (fio_hash) unexpected missing container (2).\n");
exit(-1);
}
*info = (fio_hash_data_s){.obj = NULL};
FIO_HASH_KEY_DESTROY(hash->ordered[hash->pos].key);
hash->ordered[hash->pos] =
(fio_hash_data_ordered_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
}
return old;
}
/**
* Allows a peak at the Hash's last element.
*
* If a pointer to `key` is provided, the element's key will be placed in it's
* place.
*
* Remember that keys might be destroyed if the Hash is altered
* (`FIO_HASH_KEY_DESTROY`).
*/
FIO_FUNC void *fio_hash_last(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key) {
if (key)
*key = hash->ordered[hash->pos - 1].key;
return hash->ordered[hash->pos - 1].obj;
}
/* attempts to rehash the hashmap. */
FIO_FUNC void fio_hash_rehash(fio_hash_s *h) {
if (!h->capa) /* lazy initialization */
h->mask = FIO_HASH_INITIAL_CAPACITY - 1;
retry_rehashing:
h->mask = ((h->mask) << 1) | 1;
{
/* It's better to reallocate using calloc than manually zero out memory */
/* Maybe there's enough zeroed out pages available in the system */
FIO_HASH_FREE(h->map, h->capa);
h->capa = h->mask + 1;
h->map = (fio_hash_data_s *)FIO_HASH_CALLOC(sizeof(*h->map), h->capa);
if (!h->map) {
perror("HashMap Allocation Failed");
exit(errno);
}
/* the ordered list doesn't care about initialized memory, so realloc */
/* will be faster. */
h->ordered = (fio_hash_data_ordered_s *)(FIO_HASH_REALLOC(
h->ordered, ((h->capa >> 1) * sizeof(*h->ordered)),
((h->capa) * sizeof(*h->ordered)), ((h->pos) * sizeof(*h->ordered))));
if (!h->ordered) {
perror("HashMap Reallocation Failed");
exit(errno);
}
}
if (!h->count) {
/* empty hash */
return;
} else if (h->pos == h->count) {
/* the ordered list is fully occupied, no need to rearange. */
FIO_HASH_FOR_LOOP(h, i) {
/* can't use fio_hash_insert, because we're recycling containers */
fio_hash_data_s *place = fio_hash_seek_pos_(h, i->key);
if (!place) {
goto retry_rehashing;
}
*place = (fio_hash_data_s){.key = i->key, .obj = i};
}
} else {
/* the ordered list has holes, fill 'em up.*/
size_t reader = 0;
size_t writer = 0;
while (reader < h->pos) {
if (h->ordered[reader].obj) {
fio_hash_data_s *place = fio_hash_seek_pos_(h, h->ordered[reader].key);
if (!place) {
goto retry_rehashing;
}
*place = (fio_hash_data_s){.key = h->ordered[reader].key,
.obj = h->ordered + writer};
fio_hash_data_ordered_s old = h->ordered[reader];
h->ordered[reader] =
(fio_hash_data_ordered_s){.key = FIO_HASH_KEY_INVALID, .obj = NULL};
h->ordered[writer] = old;
++writer;
} else {
FIO_HASH_KEY_DESTROY(h->ordered[reader].key);
h->ordered[reader].key = FIO_HASH_KEY_INVALID;
}
++reader;
}
h->pos = writer;
// h->ordered[h->pos] =
// (fio_hash_data_ordered_s){.key = FIO_HASH_KEY_INVALID, .obj = NULL};
}
}
FIO_FUNC inline size_t fio_hash_each(fio_hash_s *hash, size_t start_at,
int (*task)(FIO_HASH_KEY_TYPE key,
void *obj, void *arg),
void *arg) {
if (start_at >= hash->count)
return hash->count;
size_t count = 0;
if (hash->pos == hash->count) {
count = start_at;
while (count < hash->pos) {
/* no "holes" in the hash. */
++count;
if (task(hash->ordered[count - 1].key,
(void *)hash->ordered[count - 1].obj, arg) == -1)
return count;
}
} else {
size_t pos = 0;
while (count < start_at && pos < hash->pos) {
if (hash->ordered[pos].obj) {
++count;
}
++pos;
}
while (pos < hash->pos) {
if (hash->ordered[pos].obj) {
++count;
if (task(hash->ordered[pos].key, (void *)hash->ordered[pos].obj, arg) ==
-1)
return count;
}
++pos;
}
}
return count;
}
/** Returns the number of elements in the Hash. */
FIO_FUNC inline size_t fio_hash_count(const fio_hash_s *hash) {
if (!hash)
return 0;
return hash->count;
}
/**
* Returns a temporary theoretical Hash map capacity.
* This could be used for testing performance and memory consumption.
*/
FIO_FUNC inline size_t fio_hash_capa(const fio_hash_s *hash) {
if (!hash)
return 0;
return hash->capa;
}
/**
* Returns non-zero if the hash is fragmented (more than 50% holes).
*/
FIO_FUNC inline size_t fio_hash_is_fragmented(const fio_hash_s *hash) {
return (hash->pos > (hash->count << 1));
}
/**
* Attempts to minimize memory usage by removing empty spaces caused by deleted
* items and rehashing the Hash Map.
*
* Returns the updated hash map capacity.
*/
FIO_FUNC inline size_t fio_hash_compact(fio_hash_s *hash) {
if (!hash)
return 0;
if (hash->count == hash->pos && (hash->count << 1) >= hash->capa)
return hash->capa;
/* compact ordered list */
{
size_t reader = 0;
size_t writer = 0;
while (reader < hash->pos) {
if (hash->ordered[reader].obj) {
hash->ordered[writer] = hash->ordered[reader];
++writer;
} else {
FIO_HASH_KEY_DESTROY(hash->ordered[reader].key);
}
++reader;
}
hash->pos = writer;
}
/* recalculate minimal length and rehash */
while (hash->mask && hash->mask >= hash->count)
hash->mask = hash->mask >> 1;
if (hash->mask + 1 < FIO_HASH_INITIAL_CAPACITY)
hash->mask = (FIO_HASH_INITIAL_CAPACITY - 1);
while (hash->count >= hash->mask)
hash->mask = (hash->mask << 1) | 1;
fio_hash_rehash(hash);
return hash->capa;
}
#if DEBUG && !FIO_HASH_NO_TEST
#define FIO_HASHMAP_TEXT_COUNT 524288UL
#include <stdio.h>
FIO_FUNC void fio_hash_test(void) {
#define TEST_ASSERT(cond, ...) \
if (!(cond)) { \
fprintf(stderr, "* " __VA_ARGS__); \
fprintf(stderr, "\n !!! Testing failed !!!\n"); \
exit(-1); \
}
fio_hash_s h = {.capa = 0};
fprintf(stderr, "=== Testing Core HashMap (fio_hashmap.h)\n");
fprintf(stderr, "* Inserting %lu items\n", FIO_HASHMAP_TEXT_COUNT);
for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; ++i) {
fio_hash_insert(&h, i, (void *)i);
TEST_ASSERT((i == (uintptr_t)fio_hash_find(&h, i)), "insertion != find");
}
fprintf(stderr, "* Seeking %lu items\n", FIO_HASHMAP_TEXT_COUNT);
for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; ++i) {
TEST_ASSERT((i == (uintptr_t)fio_hash_find(&h, i)), "insertion != find");
}
{
fprintf(stderr, "* Testing order for %lu items\n", FIO_HASHMAP_TEXT_COUNT);
uintptr_t i = 1;
FIO_HASH_FOR_LOOP(&h, pos) {
TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
TEST_ASSERT(pos->key == i, "Key out of order %lu != %lu.",
(unsigned long)i, (unsigned long)pos->key);
++i;
}
}
fprintf(stderr, "* Removing odd items from %lu items\n",
FIO_HASHMAP_TEXT_COUNT);
for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; i += 2) {
uintptr_t old = (uintptr_t)fio_hash_insert(&h, i, NULL);
TEST_ASSERT(old == i, "Removal didn't return old value.");
TEST_ASSERT(!(fio_hash_find(&h, i)), "Removal failed (still exists).");
}
if (1) {
size_t count = h.count;
size_t pos = h.pos;
fio_hash_insert(&h, 1, (void *)1);
TEST_ASSERT(
count + 1 == h.count,
"Re-adding a removed item should increase count by 1 (%zu + 1 != %zu).",
count, (size_t)h.count);
TEST_ASSERT(
pos == h.pos,
"Re-adding a removed item shouldn't change the position marker!");
TEST_ASSERT(fio_hash_find(&h, 1) == (void *)1,
"Re-adding a removed item should update the item (%p != 1)!",
fio_hash_find(&h, 1));
fio_hash_insert(&h, 1, NULL);
TEST_ASSERT(count == h.count,
"Re-removing an item should decrease count (%zu != %zu).",
count, (size_t)h.count);
TEST_ASSERT(pos == h.pos,
"Re-removing an item shouldn't effect the position marker!");
TEST_ASSERT(!fio_hash_find(&h, 1),
"Re-removing a re-added item should update the item!");
}
{
fprintf(stderr, "* Testing for %lu / 2 holes\n", FIO_HASHMAP_TEXT_COUNT);
uintptr_t i = 1;
FIO_HASH_FOR_LOOP(&h, pos) {
if (pos->obj) {
TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
TEST_ASSERT(pos->key == i, "Key out of order %lu != %lu.",
(unsigned long)i, (unsigned long)pos->key);
} else {
TEST_ASSERT(pos->obj == NULL, "old value detected.");
TEST_ASSERT(pos->key == i, "Key out of order.");
}
++i;
}
}
{
fprintf(stderr, "* Poping two elements (testing pop through holes)\n");
FIO_HASH_KEY_TYPE k;
TEST_ASSERT(fio_hash_pop(&h, &k), "Pop 1 failed to collect object");
TEST_ASSERT(k, "Pop 1 failed to collect key");
FIO_HASH_KEY_DESTROY(k);
TEST_ASSERT(fio_hash_pop(&h, &k), "Pop 2 failed to collect object");
TEST_ASSERT(k, "Pop 2 failed to collect key");
FIO_HASH_KEY_DESTROY(k);
}
fprintf(stderr, "* Compacting Hash to %lu\n", FIO_HASHMAP_TEXT_COUNT >> 1);
fio_hash_compact(&h);
{
fprintf(stderr, "* Testing that %lu items are continuous\n",
FIO_HASHMAP_TEXT_COUNT >> 1);
uintptr_t i = 0;
FIO_HASH_FOR_LOOP(&h, pos) {
TEST_ASSERT(pos->obj, "Found a hole after compact.");
TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
++i;
}
TEST_ASSERT(i == h.count, "count error (%lu != %lu).", i, h.count);
}
fio_hash_free(&h);
TEST_ASSERT(!h.map && !h.ordered && !h.pos && !h.capa,
"Hash not re-initialized after fio_hash_free");
fio_hash_new2(&h, FIO_HASHMAP_TEXT_COUNT);
for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; ++i) {
fio_hash_insert(&h, i, (void *)i);
TEST_ASSERT((i == (uintptr_t)fio_hash_find(&h, i)),
"insertion (2nd round) != find");
}
for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; i += 2) {
uintptr_t old = (uintptr_t)fio_hash_insert(&h, i, NULL);
TEST_ASSERT(old == i, "Removal didn't return old value.");
TEST_ASSERT(!(fio_hash_find(&h, i)), "Removal failed (still exists).");
}
fio_hash_rehash(&h);
{
fprintf(stderr,
"* Testing that %lu items are continuous (after rehashing)\n",
FIO_HASHMAP_TEXT_COUNT >> 1);
uintptr_t i = 0;
FIO_HASH_FOR_LOOP(&h, pos) {
TEST_ASSERT(pos->obj, "Found a hole after compact.");
TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
++i;
}
TEST_ASSERT(i == h.count, "count error (%lu != %lu) post rehash.", i,
h.count);
}
fio_hash_free(&h);
}
#undef TEST_ASSERT
#endif /* DEBUG Testing */
#undef FIO_HASH_REALLOC
#undef FIO_HASH_CALLOC
#undef FIO_HASH_FREE
#undef FIO_FUNC
#endif /* H_FIO_SIMPLE_HASH_H */