diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3a457..2253993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `wpX64Mur3GetHash128` implementation for the x64 128-bit variant of the MurmurHash3 algorithm - `wpSip24Hasher`, `wpSip48Hasher`, `wpSip24HasherWithKey` and `wpSip48HasherWithKey` constructors for the hasher interface - `wpSipGetHash128` implementation for the 128 variant of siphash +- `wpHashSet`, `wpHashSetCustomHasher`, `wpHashSetAlloc` and `wpHashSetCustomHasherAlloc` constructors +- `wpHashSetFind`, `wpHashSetInsertCapped`, `wpHashSetInsertAlloc`, `wpHashSetRemove`, `wpHashSetItems`, `wpHashSetCapacity`, `wpHashSetCount`, `wpHashSetItemSize`, `wpHashSetClear` and `wpHashSetDealloc` utilities - `wpU8Const`, `wpU16Const`, `wpU32Const`, `wpU64Const`, `wpI8Const`, `wpI16Const`, `wpI32Const`, and `wpI64Const` aliases - `wpPrngXorshiftInitWithSeed` to allow for user control with PRNG - `wpPrngXorshift256InRange`, `wpPrngXorshift256ssInRange`, `wpPrngXorshift256pInRange`, `wpPrngXorshift256Choice`, `wpPrngXorshift256ssChoice` and `wpPrngXorshift256pChoice` utilities diff --git a/src/base/hash/set/set.c b/src/base/hash/set/set.c new file mode 100644 index 0000000..07fcc62 --- /dev/null +++ b/src/base/hash/set/set.c @@ -0,0 +1,51 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#include "set.h" +#include "../table/table.h" + +WpHashSet hashSetAlloc(const WpAllocator *allocator, WpU8StreamEncoder encoder, WpKeyEqualTest key_eq, + u64 capacity, u64 key_size) { + return hashSetAllocWithCustomHasher(allocator, WP_HASH_TABLE_DEFAULT_HASHER, encoder, key_eq, + capacity, key_size); +} + +WpHashSet hashSetAllocWithCustomHasher(const WpAllocator *allocator, WpHasher hasher, + WpU8StreamEncoder encoder, WpKeyEqualTest key_eq, u64 capacity, + u64 key_size) { + WpHashSet set = {0}; + hashTableAlloc(allocator, &set, hasher, encoder, key_eq, capacity, key_size, 0, false); + return set; +} + +WpHashLookupResult hashSetFind(WpHashSet *set, void *item, u64 item_size) { + u64 hash = hashTableCalcHash(set, item, item_size); + WpHashBucketRef ref = hashTableBucketRef(set, hash); + return hashTableLookup(set, item, ref, item_size); +} + +void hashSetInsertCapped(WpHashSet *set, void *item, u64 item_size) { + WpHashLookupResult result = hashSetFind(set, item, item_size); + if (!result.found && set->load_factor < WP_HASH_MAX_LOAD_FACTOR) { + hashTableInsert(set, item, NULL, result.ref, item_size, 0); + } +} + +void hashSetInsertAlloc(const WpAllocator *allocator, WpHashSet *set, void *item, u64 item_size) { + WpHashLookupResult result = hashSetFind(set, item, item_size); + if (!result.found) { + if (set->load_factor >= WP_HASH_MAX_LOAD_FACTOR) { + hashTableGrow(allocator, set, item_size, 0); + // Rerun item lookup after growing + result = hashSetFind(set, item, item_size); + } + + hashTableInsert(set, item, NULL, result.ref, item_size, 0); + } +} + +void hashSetRemove(WpHashSet *set, void *item, u64 item_size) { + WpHashLookupResult result = hashSetFind(set, item, item_size); + if (result.found) { + hashTableRemove(set, result.ref, item_size, 0); + } +} diff --git a/src/base/hash/set/set.h b/src/base/hash/set/set.h new file mode 100644 index 0000000..782d12a --- /dev/null +++ b/src/base/hash/set/set.h @@ -0,0 +1,105 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#ifndef SET_H +#define SET_H + +#include "../table/table.h" +#include "../../../common/misc/misc_utils.h" +#include "../../../common/platform/platform.h" + +#ifdef WP_PLATFORM_CPP +BEGIN_C_LINKAGE +#endif // !WP_PLATFORM_CPP + +typedef WpHashTable WpHashSet; + +#ifdef WP_PLATFORM_CPP +#define wpHashSet(TYPE, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY) \ + (WpHashSet{ \ + WP_HASH_TABLE_DEFAULT_HASHER, \ + U8_STREAM_ENCODER, \ + ITEM_EQ_TEST, \ + wpArrayWithCapacity(TYPE, wpMiscUtilsU64RoundUpPow2(CAPACITY), WP_ARRAY_INIT_NONE), \ + NULL, \ + wpArrayWithCapacity(WpHashBucket, wpMiscUtilsU64RoundUpPow2(CAPACITY), WP_ARRAY_INIT_FILLED), \ + WP_HASH_TABLE_MAGIC, \ + wpMiscUtilsU64RoundUpPow2(CAPACITY) - 1, \ + 0.0f, \ + false, \ + }) +#define wpHashSetCustomHasher(TYPE, HASHER, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY) \ + (WpHashSet{ \ + HASHER, \ + U8_STREAM_ENCODER, \ + ITEM_EQ_TEST, \ + wpArrayWithCapacity(TYPE, wpMiscUtilsU64RoundUpPow2(CAPACITY), WP_ARRAY_INIT_NONE), \ + NULL, \ + wpArrayWithCapacity(WpHashBucket, wpMiscUtilsU64RoundUpPow2(CAPACITY), WP_ARRAY_INIT_FILLED), \ + WP_HASH_TABLE_MAGIC, \ + wpMiscUtilsU64RoundUpPow2(CAPACITY) - 1, \ + 0.0f, \ + false, \ + }) +#else +#define wpHashSet(TYPE, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY) \ + ((WpHashSet){ \ + .hasher = WP_HASH_TABLE_DEFAULT_HASHER, \ + .encoder = U8_STREAM_ENCODER, \ + .key_eq = ITEM_EQ_TEST, \ + .keys = wpArrayWithCapacity(TYPE, wpMiscUtilsU64RoundUpPow2(CAPACITY), \ + WP_ARRAY_INIT_NONE), \ + .buckets = wpArrayWithCapacity(WpHashBucket, wpMiscUtilsU64RoundUpPow2(CAPACITY), \ + WP_ARRAY_INIT_FILLED), \ + .magic = WP_HASH_TABLE_MAGIC, \ + .bucket_mask = wpMiscUtilsU64RoundUpPow2(CAPACITY) - 1, \ + }) +#define wpHashSetCustomHasher(TYPE, HASHER, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY) \ + ((WpHashSet){ \ + .hasher = HASHER, \ + .encoder = U8_STREAM_ENCODER, \ + .key_eq = ITEM_EQ_TEST, \ + .keys = wpArrayWithCapacity(TYPE, wpMiscUtilsU64RoundUpPow2(CAPACITY), \ + WP_ARRAY_INIT_NONE), \ + .buckets = wpArrayWithCapacity(WpHashBucket, wpMiscUtilsU64RoundUpPow2(CAPACITY), \ + WP_ARRAY_INIT_FILLED), \ + .magic = WP_HASH_TABLE_MAGIC, \ + .bucket_mask = wpMiscUtilsU64RoundUpPow2(CAPACITY) - 1, \ + }) +#endif + +#define wpHashSetAlloc(TYPE, ALLOCATOR_PTR, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY) \ + hashSetAlloc(ALLOCATOR_PTR, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY, sizeof(TYPE)) +#define wpHashSetCustomHasherAlloc(TYPE, ALLOCATOR_PTR, HASHER, U8_STREAM_ENCODER, ITEM_EQ_TEST, \ + CAPACITY) \ + hashSetAllocWithCustomHasher(ALLOCATOR_PTR, HASHER, U8_STREAM_ENCODER, ITEM_EQ_TEST, CAPACITY, \ + sizeof(TYPE)) +#define wpHashSetFind(TYPE, SET_PTR, ITEM_PTR) hashSetFind(SET_PTR, ITEM_PTR, sizeof(TYPE)) +#define wpHashSetInsertCapped(TYPE, SET_PTR, ITEM_PTR) \ + hashSetInsertCapped(SET_PTR, ITEM_PTR, sizeof(TYPE)) +#define wpHashSetInsertAlloc(TYPE, ALLOCATOR_PTR, SET_PTR, ITEM_PTR) \ + hashSetInsertAlloc(ALLOCATOR_PTR, SET_PTR, ITEM_PTR, sizeof(TYPE)) +#define wpHashSetRemove(TYPE, SET_PTR, ITEM_PTR) hashSetRemove(SET_PTR, ITEM_PTR, sizeof(TYPE)) +#define wpHashSetItems(TYPE, SET_PTR) ((TYPE *)hashTableKeys(SET_PTR, sizeof(TYPE))) +#define wpHashSetCapacity(SET_PTR) hashTableCapacity(SET_PTR) +#define wpHashSetCount(SET_PTR) hashTableCount(SET_PTR) +#define wpHashSetItemSize(SET_PTR) hashTableKeySize(SET_PTR) +#define wpHashSetClear(TYPE, SET_PTR) hashTableClear(SET_PTR, sizeof(TYPE), 0) +#define wpHashSetDealloc(TYPE, ALLOCATOR_PTR, SET_PTR) \ + hashTableDealloc(ALLOCATOR_PTR, SET_PTR, sizeof(TYPE), 0) + +WpHashSet hashSetAlloc(const WpAllocator *allocator, WpU8StreamEncoder encoder, + WpKeyEqualTest key_eq, u64 capacity, u64 key_size); +WpHashSet hashSetAllocWithCustomHasher(const WpAllocator *allocator, WpHasher hasher, + WpU8StreamEncoder encoder, WpKeyEqualTest key_eq, + u64 capacity, u64 key_size); +WpHashLookupResult hashSetFind(WpHashSet *set, void *item, u64 item_size); +void hashSetInsertCapped(WpHashSet *set, void *item, u64 item_size); +void hashSetInsertAlloc(const WpAllocator *allocator, WpHashSet *set, void *item, + u64 item_size); +void hashSetRemove(WpHashSet *set, void *item, u64 item_size); + +#ifdef WP_PLATFORM_CPP +END_C_LINKAGE +#endif // !WP_PLATFORM_CPP + +#endif // !SET_H diff --git a/src/base/hash/table/table.c b/src/base/hash/table/table.c index 59b9228..285a378 100644 --- a/src/base/hash/table/table.c +++ b/src/base/hash/table/table.c @@ -8,26 +8,62 @@ #include "../../../common/assert/assert.h" #include "../../../common/misc/misc_utils.h" -wp_persist inline void hashTableValidate(const WpHashTable *table, u64 key_size, u64 value_size); +wp_persist inline void *hashTableGetKeyFromBucket(WpHashTable *table, WpHashBucket bucket, u64 key_size); +wp_persist inline void *hashTableGetValueFromBucket(WpHashTable *table, WpHashBucket bucket, u64 value_size); +wp_persist inline u64 hashTableGetDist(u64 dist_and_tag); +wp_persist inline u64 hashTableNextBucket(WpHashTable *table, u64 bucket_index); +wp_persist inline u64 hashTableDistInc(u64 dist_and_tag); +wp_persist inline u64 hashTableDistDec(u64 dist_and_tag); +wp_persist inline void hashTableValidate(const WpHashTable *table, u64 key_size, u64 value_size); -void *hashTableKeys(WpHashTable *table) { +void hashTableAlloc(const WpAllocator *allocator, WpHashTable *table, WpHasher hasher, + WpU8StreamEncoder encoder, WpKeyEqualTest key_eq, u64 capacity, u64 key_size, + u64 value_size, b8 has_values) { + wpDebugAssert(allocator != NULL && table != NULL && encoder != NULL && key_eq != NULL, + "`allocator`, `table`, `encoder` and `key_eq` should not be NULL"); + + u64 table_capacity = wpMiscUtilsU64RoundUpPow2(capacity); + table->hasher = hasher; + table->encoder = encoder; + table->key_eq = key_eq; + table->keys = arrayAllocCapacity(allocator, table_capacity, WP_ARRAY_INIT_NONE, key_size);; + table->buckets = wpArrayAllocCapacity(WpHashBucket, allocator, table_capacity, WP_ARRAY_INIT_FILLED); + table->magic = WP_HASH_TABLE_MAGIC; + table->bucket_mask = table_capacity - 1; + table->load_factor = WP_HASH_MAX_LOAD_FACTOR; + table->has_values = has_values; + + wpRuntimeAssert(table->keys != NULL && table->buckets != NULL, "Failed to allocate keys, buckets or both"); + + if (has_values) { + table->values = arrayAllocCapacity(allocator, table_capacity, WP_ARRAY_INIT_NONE, value_size);; + wpRuntimeAssert(table->values != NULL, "Failed to allocate values"); + } +} + +u64 hashTableCalcHash(WpHashTable *table, void *key, u64 key_size) { + hashTableValidate(table, key_size, 0); + wpDebugAssert(key != NULL, "`key` should not be NULL"); + WpU8Stream stream = table->encoder(key); + return wpHasherGetHash64(&table->hasher, &stream); +} + +WpHashBucketRef hashTableBucketRef(WpHashTable *table, u64 hash) { hashTableValidate(table, 0, 0); + return (WpHashBucketRef){ + .dist_and_tag = WP_HASH_DIST_INC | (hash & WP_HASH_TAG_MASK), + .bucket_index = hash & table->bucket_mask, + }; +} + +void *hashTableKeys(WpHashTable *table, u64 key_size) { + hashTableValidate(table, key_size, 0); return table->keys; } -void *hashTableValues(WpHashTable *table) { - hashTableValidate(table, 0, 0); - return table->values; -} - -void *hashTableGetKeyFromBucket(WpHashTable *table, WpHashBucket bucket, u64 key_size) { - hashTableValidate(table, key_size, 0); - return arrayGet(table->keys, bucket.item_index, key_size); -} - -void *hashTableGetValueFromBucket(WpHashTable *table, WpHashBucket bucket, u64 value_size) { +void *hashTableValues(WpHashTable *table, u64 value_size) { hashTableValidate(table, 0, value_size); - return table->has_values ? arrayGet(table->values, bucket.item_index, value_size) : NULL; + return table->values; } void *hashTableGetKeyFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 key_size) { @@ -58,25 +94,21 @@ u64 hashTableValueSize(WpHashTable *table) { return table->has_values ? wpArrayItemSize(table->values) : 0; } -u64 hashTableCalcHash(WpHashTable *table, void *key, u64 key_size) { - hashTableValidate(table, key_size, 0); - wpDebugAssert(key != NULL, "`key` should not be NULL"); - WpU8Stream stream = table->encoder(key); - return wpHasherGetHash64(&table->hasher, &stream); -} - -void hashTableGrow(WpHashTable *table, const WpAllocator *allocator, u64 key_size, u64 value_size) { +void hashTableGrow(const WpAllocator *allocator, WpHashTable *table, u64 key_size, u64 value_size) { hashTableValidate(table, key_size, value_size); wpDebugAssert(allocator != NULL, "`allocator` should not be NULL"); - u64 capacity = hashTableCapacity(table) * 2; + u64 capacity = wpMiscUtilsU64RoundUpPow2(hashTableCapacity(table) * 2); u64 mask = capacity - wpU64Const(1); WpArray keys = arrayAllocCapacity(allocator, capacity, WP_ARRAY_INIT_NONE, key_size); WpArray values = NULL; WpHashBucketArray buckets = wpArrayAllocCapacity(WpHashBucket, allocator, capacity, WP_ARRAY_INIT_FILLED); + wpRuntimeAssert(keys != NULL && buckets != NULL, "Failed to allocate keys, buckets or both"); + if (table->has_values) { values = arrayAllocCapacity(allocator, capacity, WP_ARRAY_INIT_NONE, value_size); + wpRuntimeAssert(values != NULL, "Failed to allocate values"); } WpArray old_keys = table->keys; @@ -212,27 +244,39 @@ void hashTableClear(WpHashTable *table, u64 key_size, u64 value_size) { wpArrayZero(WpHashBucket, table->buckets); } -WpHashBucketRef hashTableBucketRef(WpHashTable *table, u64 hash) { - hashTableValidate(table, 0, 0); - return (WpHashBucketRef){ - .dist_and_tag = WP_HASH_DIST_INC | (hash & WP_HASH_TAG_MASK), - .bucket_index = hash & table->bucket_mask, - }; +void hashTableDealloc(const WpAllocator *allocator, WpHashTable *table, u64 key_size, u64 value_size) { + wpDebugAssert(allocator != NULL, "`allocator` should not be NULL"); + hashTableValidate(table, key_size, value_size); + arrayDealloc(allocator, table->keys, key_size); + wpArrayDealloc(WpHashBucket, allocator, table->buckets); + if (table->has_values) { + arrayDealloc(allocator, table->values, value_size); + } } -u64 hashTableGetDist(u64 dist_and_tag) { +wp_persist inline void *hashTableGetKeyFromBucket(WpHashTable *table, WpHashBucket bucket, u64 key_size) { + hashTableValidate(table, key_size, 0); + return arrayGet(table->keys, bucket.item_index, key_size); +} + +wp_persist inline void *hashTableGetValueFromBucket(WpHashTable *table, WpHashBucket bucket, u64 value_size) { + hashTableValidate(table, 0, value_size); + return table->has_values ? arrayGet(table->values, bucket.item_index, value_size) : NULL; +} + +wp_persist inline u64 hashTableGetDist(u64 dist_and_tag) { return dist_and_tag & (~WP_HASH_TAG_MASK); } -u64 hashTableNextBucket(WpHashTable *table, u64 bucket_index) { +wp_persist inline u64 hashTableNextBucket(WpHashTable *table, u64 bucket_index) { return (++bucket_index) & table->bucket_mask; } -u64 hashTableDistInc(u64 dist_and_tag) { +wp_persist inline u64 hashTableDistInc(u64 dist_and_tag) { return dist_and_tag + WP_HASH_DIST_INC; } -u64 hashTableDistDec(u64 dist_and_tag) { +wp_persist inline u64 hashTableDistDec(u64 dist_and_tag) { return dist_and_tag - WP_HASH_DIST_INC; } diff --git a/src/base/hash/table/table.h b/src/base/hash/table/table.h index 11ceec1..30ce60c 100644 --- a/src/base/hash/table/table.h +++ b/src/base/hash/table/table.h @@ -69,18 +69,21 @@ typedef struct WpHashLookupResult { b8 found; } WpHashLookupResult; -void *hashTableKeys(WpHashTable *table); -void *hashTableValues(WpHashTable *table); -void *hashTableGetKeyFromBucket(WpHashTable *table, WpHashBucket bucket, u64 key_size); -void *hashTableGetValueFromBucket(WpHashTable *table, WpHashBucket bucket, u64 value_size); +void hashTableAlloc(const WpAllocator *allocator, WpHashTable *table, + WpHasher hasher, WpU8StreamEncoder encoder, + WpKeyEqualTest key_eq, u64 capacity, u64 key_size, + u64 value_size, b8 has_values); +u64 hashTableCalcHash(WpHashTable *table, void *key, u64 key_size); +WpHashBucketRef hashTableBucketRef(WpHashTable *table, u64 hash); +void *hashTableKeys(WpHashTable *table, u64 key_size); +void *hashTableValues(WpHashTable *table, u64 value_size); void *hashTableGetKeyFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 key_size); void *hashTableGetValueFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 value_size); u64 hashTableCapacity(WpHashTable *table); u64 hashTableCount(WpHashTable *table); u64 hashTableKeySize(WpHashTable *table); u64 hashTableValueSize(WpHashTable *table); -u64 hashTableCalcHash(WpHashTable *table, void *key, u64 key_size); -void hashTableGrow(WpHashTable *table, const WpAllocator *allocator, u64 key_size, +void hashTableGrow(const WpAllocator *allocator, WpHashTable *table, u64 key_size, u64 value_size); WpHashLookupResult hashTableLookup(WpHashTable *table, void *key, WpHashBucketRef ref, u64 key_size); void hashTableUpdate(WpHashTable *table, void *value, WpHashBucketRef ref, u64 value_size); @@ -88,11 +91,8 @@ void hashTableInsert(WpHashTable *table, void *key, void *value, W u64 key_size, u64 value_size); void hashTableRemove(WpHashTable *table, WpHashBucketRef ref, u64 key_size, u64 value_size); void hashTableClear(WpHashTable *table, u64 key_size, u64 value_size); -WpHashBucketRef hashTableBucketRef(WpHashTable *table, u64 hash); -u64 hashTableGetDist(u64 dist_and_tag); -u64 hashTableNextBucket(WpHashTable *table, u64 bucket_index); -u64 hashTableDistInc(u64 dist_and_tag); -u64 hashTableDistDec(u64 dist_and_tag); +void hashTableDealloc(const WpAllocator *allocator, WpHashTable *table, u64 key_size, + u64 value_size); #ifdef WP_PLATFORM_CPP END_C_LINKAGE diff --git a/src/base/wapp_base.c b/src/base/wapp_base.c index 297a368..10671aa 100644 --- a/src/base/wapp_base.c +++ b/src/base/wapp_base.c @@ -10,6 +10,7 @@ #include "hash/hasher/murmur3.c" #include "hash/hasher/siphash.c" #include "hash/table/table.c" +#include "hash/set/set.c" #include "queue/queue.c" #include "stream/stream.c" #include "mem/allocator/mem_allocator.c" diff --git a/src/base/wapp_base.h b/src/base/wapp_base.h index 940bb23..84b6168 100644 --- a/src/base/wapp_base.h +++ b/src/base/wapp_base.h @@ -9,6 +9,7 @@ #include "hash/hasher/murmur3.h" #include "hash/hasher/siphash.h" #include "hash/table/table.h" +#include "hash/set/set.h" #include "queue/queue.h" #include "stream/stream.h" #include "mem/allocator/mem_allocator.h" diff --git a/tests/hashset/test_hashset.c b/tests/hashset/test_hashset.c new file mode 100644 index 0000000..f626fb3 --- /dev/null +++ b/tests/hashset/test_hashset.c @@ -0,0 +1,160 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#include "wapp.h" + +wp_persist inline WpU8Stream encoder(void *data); +wp_persist inline b8 key_eq(void *a, void *b); + +WpTestFuncResult test_hashset(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 8); + result = result && wpHashSetCapacity(&set) == 8 && wpHashSetCount(&set) == 0; + set = wpHashSet(WpStr8, encoder, key_eq, 3); + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 0; + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_alloc(void) { + WpAllocator arena = wpMemArenaAllocatorInit(KiB(4)); + b8 result = true; + WpHashSet set = wpHashSetAlloc(WpStr8, &arena, encoder, key_eq, 8); + result = result && wpHashSetCapacity(&set) == 8 && wpHashSetCount(&set) == 0; + set = wpHashSetAlloc(WpStr8, &arena, encoder, key_eq, 3); + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 0; + + wpMemArenaAllocatorDestroy(&arena); + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_find(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 8); + WpStr8Array keys = wpArray( + WpStr8, + wpStr8Lit("key1"), + wpStr8Lit("key2"), + wpStr8Lit("key3"), + wpStr8Lit("key4") + ); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + WpHashLookupResult res = wpHashSetFind(WpStr8, &set, &keys[3]); + + result = result && res.found && res.ref.dist_and_tag > 0; + + WpStr8 key = wpStr8Lit("key0"); + res = wpHashSetFind(WpStr8, &set, &key); + result = result && !res.found; + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_insert_capped(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 4); + WpStr8Array keys = wpArray( + WpStr8, + wpStr8Lit("key1"), + wpStr8Lit("key2"), + wpStr8Lit("key3"), + wpStr8Lit("key4") + ); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 3; + for (u64 i = 0; i < 3; ++i) { + result = result && wpStr8Equal(wpArrayGet(WpStr8, set.keys, i), &keys[i]); + } + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_insert_alloc(void) { + WpAllocator arena = wpMemArenaAllocatorInit(KiB(4)); + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 4); + WpStr8Array keys = wpArray( + WpStr8, + wpStr8Lit("key1"), + wpStr8Lit("key2"), + wpStr8Lit("key3"), + wpStr8Lit("key4") + ); + + WpStr8Array set_keys = set.keys; + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertAlloc(WpStr8, &arena, &set, &keys[i]); + } + + result = result && set.keys != set_keys && wpHashSetCapacity(&set) == 8 && wpHashSetCount(&set) == 4; + for (u64 i = 0; i < 3; ++i) { + result = result && wpStr8Equal(&wpHashSetItems(WpStr8, &set)[i], &keys[i]); + } + + wpMemArenaAllocatorDestroy(&arena); + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_remove(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 4); + WpStr8Array keys = wpArray( + WpStr8, + wpStr8Lit("key1"), + wpStr8Lit("key2"), + wpStr8Lit("key3"), + wpStr8Lit("key4") + ); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + wpHashSetRemove(WpStr8, &set, &keys[0]); + + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 2; + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_clear(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 8); + WpStr8Array keys = wpArray( + WpStr8, + wpStr8Lit("key1"), + wpStr8Lit("key2"), + wpStr8Lit("key3"), + wpStr8Lit("key4") + ); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + wpHashSetClear(WpStr8, &set); + + result = result && wpHashSetCount(&set) == 0; + + return wpTesterResult(result); +} + +wp_persist inline WpU8Stream encoder(void *data) { + WpStr8 *str = (WpStr8 *)data; + return wpStream(u8, str->buf, str->size); +} + +wp_persist inline b8 key_eq(void *a, void *b) { + WpStr8 *a_str = (WpStr8 *)a; + WpStr8 *b_str = (WpStr8 *)b; + return wpStr8Equal(a_str, b_str); +} diff --git a/tests/hashset/test_hashset.cc b/tests/hashset/test_hashset.cc new file mode 100644 index 0000000..7441d2b --- /dev/null +++ b/tests/hashset/test_hashset.cc @@ -0,0 +1,150 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#include "wapp.h" + +wp_persist inline WpU8Stream encoder(void *data); +wp_persist inline b8 key_eq(void *a, void *b); + +WpTestFuncResult test_hashset(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 8); + result = result && wpHashSetCapacity(&set) == 8 && wpHashSetCount(&set) == 0; + set = wpHashSet(WpStr8, encoder, key_eq, 3); + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 0; + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_alloc(void) { + WpAllocator arena = wpMemArenaAllocatorInit(KiB(4)); + b8 result = true; + WpHashSet set = wpHashSetAlloc(WpStr8, &arena, encoder, key_eq, 8); + result = result && wpHashSetCapacity(&set) == 8 && wpHashSetCount(&set) == 0; + set = wpHashSetAlloc(WpStr8, &arena, encoder, key_eq, 3); + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 0; + + wpMemArenaAllocatorDestroy(&arena); + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_find(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 8); + WpStr8 key1 = wpStr8Lit("key1"); + WpStr8 key2 = wpStr8Lit("key2"); + WpStr8 key3 = wpStr8Lit("key3"); + WpStr8 key4 = wpStr8Lit("key4"); + WpStr8Array keys = wpArray(WpStr8, key1, key2, key3, key4); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + WpHashLookupResult res = wpHashSetFind(WpStr8, &set, &keys[3]); + + result = result && res.found && res.ref.dist_and_tag > 0; + + WpStr8 key = wpStr8Lit("key0"); + res = wpHashSetFind(WpStr8, &set, &key); + result = result && !res.found; + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_insert_capped(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 4); + WpStr8 key1 = wpStr8Lit("key1"); + WpStr8 key2 = wpStr8Lit("key2"); + WpStr8 key3 = wpStr8Lit("key3"); + WpStr8 key4 = wpStr8Lit("key4"); + WpStr8Array keys = wpArray(WpStr8, key1, key2, key3, key4); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 3; + for (u64 i = 0; i < 3; ++i) { + result = result && wpStr8Equal(wpArrayGet(WpStr8, set.keys, i), &keys[i]); + } + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_insert_alloc(void) { + WpAllocator arena = wpMemArenaAllocatorInit(KiB(4)); + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 4); + WpStr8 key1 = wpStr8Lit("key1"); + WpStr8 key2 = wpStr8Lit("key2"); + WpStr8 key3 = wpStr8Lit("key3"); + WpStr8 key4 = wpStr8Lit("key4"); + WpStr8Array keys = wpArray(WpStr8, key1, key2, key3, key4); + + WpStr8Array set_keys = (WpStr8Array)set.keys; + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertAlloc(WpStr8, &arena, &set, &keys[i]); + } + + result = result && set.keys != set_keys && wpHashSetCapacity(&set) == 8 && wpHashSetCount(&set) == 4; + for (u64 i = 0; i < 3; ++i) { + result = result && wpStr8Equal(&wpHashSetItems(WpStr8, &set)[i], &keys[i]); + } + + wpMemArenaAllocatorDestroy(&arena); + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_remove(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 4); + WpStr8 key1 = wpStr8Lit("key1"); + WpStr8 key2 = wpStr8Lit("key2"); + WpStr8 key3 = wpStr8Lit("key3"); + WpStr8 key4 = wpStr8Lit("key4"); + WpStr8Array keys = wpArray(WpStr8, key1, key2, key3, key4); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + wpHashSetRemove(WpStr8, &set, &keys[0]); + + result = result && wpHashSetCapacity(&set) == 4 && wpHashSetCount(&set) == 2; + + return wpTesterResult(result); +} + +WpTestFuncResult test_hashset_clear(void) { + b8 result = true; + WpHashSet set = wpHashSet(WpStr8, encoder, key_eq, 8); + WpStr8 key1 = wpStr8Lit("key1"); + WpStr8 key2 = wpStr8Lit("key2"); + WpStr8 key3 = wpStr8Lit("key3"); + WpStr8 key4 = wpStr8Lit("key4"); + WpStr8Array keys = wpArray(WpStr8, key1, key2, key3, key4); + + for (u64 i = 0; i < wpArrayCount(keys); ++i) { + wpHashSetInsertCapped(WpStr8, &set, &keys[i]); + } + + wpHashSetClear(WpStr8, &set); + + result = result && wpHashSetCount(&set) == 0; + + return wpTesterResult(result); +} + +wp_persist inline WpU8Stream encoder(void *data) { + WpStr8 *str = (WpStr8 *)data; + return wpStream(u8, str->buf, str->size); +} + +wp_persist inline b8 key_eq(void *a, void *b) { + WpStr8 *a_str = (WpStr8 *)a; + WpStr8 *b_str = (WpStr8 *)b; + return wpStr8Equal(a_str, b_str); +} diff --git a/tests/hashset/test_hashset.h b/tests/hashset/test_hashset.h new file mode 100644 index 0000000..608330c --- /dev/null +++ b/tests/hashset/test_hashset.h @@ -0,0 +1,16 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#ifndef TEST_HASHSET_H +#define TEST_HASHSET_H + +#include "wapp.h" + +WpTestFuncResult test_hashset(void); +WpTestFuncResult test_hashset_alloc(void); +WpTestFuncResult test_hashset_find(void); +WpTestFuncResult test_hashset_insert_capped(void); +WpTestFuncResult test_hashset_insert_alloc(void); +WpTestFuncResult test_hashset_remove(void); +WpTestFuncResult test_hashset_clear(void); + +#endif // !TEST_HASHSET_H diff --git a/tests/wapptest.c b/tests/wapptest.c index 8ab9311..aed2675 100644 --- a/tests/wapptest.c +++ b/tests/wapptest.c @@ -9,6 +9,7 @@ #include "test_cpath.h" #include "test_file.h" #include "test_hasher.h" +#include "test_hashset.h" #include "test_shell_commander.h" #include "wapp.h" #include @@ -105,6 +106,13 @@ int main(void) { test_wapp_file_remove, test_murmur3, test_siphash_128, + test_hashset, + test_hashset_alloc, + test_hashset_find, + test_hashset_insert_capped, + test_hashset_insert_alloc, + test_hashset_remove, + test_hashset_clear, test_commander_cmd_success, test_commander_cmd_failure, test_commander_cmd_out_buf_success, diff --git a/tests/wapptest.cc b/tests/wapptest.cc index 8ab9311..aed2675 100644 --- a/tests/wapptest.cc +++ b/tests/wapptest.cc @@ -9,6 +9,7 @@ #include "test_cpath.h" #include "test_file.h" #include "test_hasher.h" +#include "test_hashset.h" #include "test_shell_commander.h" #include "wapp.h" #include @@ -105,6 +106,13 @@ int main(void) { test_wapp_file_remove, test_murmur3, test_siphash_128, + test_hashset, + test_hashset_alloc, + test_hashset_find, + test_hashset_insert_capped, + test_hashset_insert_alloc, + test_hashset_remove, + test_hashset_clear, test_commander_cmd_success, test_commander_cmd_failure, test_commander_cmd_out_buf_success,