diff --git a/include/ucore/htable.h b/include/ucore/htable.h new file mode 100644 index 0000000..29d935e --- /dev/null +++ b/include/ucore/htable.h @@ -0,0 +1,214 @@ +#ifndef UC_HTABLE_H_ +#define UC_HTABLE_H_ + +#include + +/** Building block for hash table. + * The hash table is stored using a chained list of buckets. + * Nodes with different hash value can be stored in the same bucket. + * + * + * The hash table manages only the hash value, not the equality + * between nodes. + * + * It's up to the user to check for duplicate values + * before adding nodes if dublicates are not desired. + * + * Finding a node must be done in a user specific way, + * normally by iterating over all nodes with the desidred + * hash, and deciding in some other way whether it is the desidred + * node. + */ + +/** Node the hashtable tracks. + * Will normally be embedded inside another struct + */ +struct UHNode { + //cached value of the hash + size_t hash; + //pointer to next in same bucket + struct UHNode *next; +}; + +/** + * The hash table struct + */ +struct UHTable { + size_t cnt_elements; + size_t cnt_buckets; + struct UHNode **buckets; +}; + +/** + * Initialize a hash table. + * @param table hash table to initialize + * @param buckets number of buckets + * @return 0 on success, errno if malloc fails or buckets == 0 + */ +int uc_htable_init(struct UHTable *table, size_t buckets); + +/** + * Frees resources of the hash table. + * @param table hash table to free (does not free @table itself + */ +void uc_htable_destroy(struct UHTable *table); + + +/** + * @param table + * @param hash find the bucket from this hash value + * @return the bucket number for the given hash + */ +static inline size_t uc_htable_bucket(const struct UHTable *table, size_t hash) +{ + return hash % table->cnt_buckets; +} + +/** + * Get the first bucket node for the given hash. + * The returned node might not have the same hash, since different + * nodes can share the same bucket. + * + * @param table table + * @param hash hash + */ +static inline struct UHNode *uc_htable_first_bucket(const struct UHTable *table, size_t hash) +{ + size_t bucket = uc_htable_bucket(table, hash); + + return table->buckets[bucket]; +} + +/** + * Get the next node in the bucket. + * @return next node or NULL + */ +static inline struct UHNode *uc_htable_next_bucket(const struct UHNode *node) +{ + return node->next; +} + +/** + * Get the first node that have the given hash + * @param table table + * @param hash hash + * @return first node with the given hash or NULL + */ +struct UHNode *uc_htable_first_hash(const struct UHTable *table, size_t hash); + + +//internal helper +struct UHNode *uc_htable_next_hash_hlp(struct UHNode *next, size_t hash); +/** + * Get the next node with the same hash + * @param node previous node + * @return next node with the same hash as node or NULL + */ +static inline struct UHNode *uc_htable_next_hash(const struct UHNode *node) +{ + return uc_htable_next_hash_hlp(node->next, node->hash); +} + + +//internal helper +struct UHNode *uc_htable_next_hlp(struct UHTable *table, size_t bucket); + +/** Get the first node in the hash table + * + * @param table table + * @return first node or NULL + */ +static inline struct UHNode *uc_htable_first(struct UHTable *table) +{ + return uc_htable_next_hlp(table, 0); +} + +/** Get the next node in the hash table + * + * @param table table + * @param node previous node + * @return first node or NULL + */ +struct UHNode *uc_htable_next(struct UHTable *table, struct UHNode *node); + +/** + * Swap 2 hash tables. + * + * @param a first table + * @param b second table + */ +void uc_htable_swap(struct UHTable *a, struct UHTable *b); + +#define UC_HTABLE_FOREACH_BUCKET(table, node_iter, hash)\ + for ((node_iter) = uc_htable_first_bucket((table), (hash));\ + (node_iter) != NULL;\ + (node_iter) = uc_htable_next_bucket((node_iter))) + +#define UC_HTABLE_FOREACH_HASH(table, node_iter, hash)\ + for ((node_iter) = uc_htable_first_hash((table), (hash));\ + (node_iter) != NULL;\ + (node_iter) = uc_htable_next_hash((node_iter))) + +#define UC_HTABLE_FOREACH(table, node_iter, hash)\ + for ((node_iter) = uc_htable_first((table));\ + (node_iter) != NULL;\ + (node_iter) = uc_htable_next((table), (node_iter))) + + +/** + * Check if the given node pointer exists in the hash table + * + * @param table table + * @param node node to check for + * @return 0 if the node does not exist in table non-0 if it does + */ +int uc_htable_has_node(struct UHTable *table, struct UHNode *node); + + +/** + * + * @return 0 if table is empty non-0 if it contains any nodes + */ +static inline int uc_htable_isempty(const struct UHTable *table) +{ + return table->cnt_elements == 0; +} + +/** + * Get number of elements in the hash table. + * + * @rparam table table + * @return number of elements + */ +static inline size_t uc_htable_count(const struct UHTable *table) +{ + return table->cnt_elements; +} + +/** Clears all nodes from the hash table, making it empty. + * No memory is released. + * @param table table to clear + */ +void uc_htable_clear(struct UHTable *table); + +/** Insert a new node. + * The new node pointer must exist as long as it is part of the + * hash table. The hash table does not check for duplicates in any way. + * + * @param table table to insert into + * @param node new node to insert. + * @param hash hash value of the new node + */ +void uc_htable_insert(struct UHTable *table, struct UHNode *node, size_t hash); + +/** Resize the hash table. This changes the number of buckets, and re-inserts + * all nodes. + * + * @param table table to resize + * @param buckets new number of buckets, must be > 0 + * @return 0 on success, errno if malloc fails or buckets == 0 + */ +int uc_htable_resize(struct UHTable *table, size_t buckets); + + +#endif diff --git a/src/htable.c b/src/htable.c new file mode 100644 index 0000000..fbc2c4e --- /dev/null +++ b/src/htable.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include "ucore/htable.h" + +int uc_htable_init(struct UHTable *table, size_t buckets) +{ + if (buckets == 0) { + return EINVAL; + } + + table->buckets = calloc(buckets, sizeof *table->buckets); + if (table->buckets == NULL) { + return ENOMEM; + } + + table->cnt_buckets = buckets; + table->cnt_elements = 0; + + return 0; +} + +void uc_htable_destroy(struct UHTable *table) +{ + free(table->buckets); + table->buckets = NULL; + table->cnt_elements = table->cnt_buckets = -1; +} + +struct UHNode *uc_htable_first_hash(const struct UHTable *table, size_t hash) +{ + size_t bucket = uc_htable_bucket(table, hash); + struct UHNode *it = table->buckets[bucket]; + + while (it && it->hash != hash) { + it = it->next; + } + + return it; +} + +struct UHNode *uc_htable_next_hash_hlp(struct UHNode *next, size_t hash) +{ + while (next && next->hash != hash) { + next = next->next; + } + + return next; +} + +struct UHNode *uc_htable_next_hlp(struct UHTable *table, size_t bucket) +{ + size_t i; + struct UHNode *found = NULL; + + for (i = bucket; i < table->cnt_buckets; i++) { + found = uc_htable_first_bucket(table, i); + if (found != NULL) { + break; + } + } + return found; +} + +struct UHNode *uc_htable_next(struct UHTable *table, struct UHNode *node) +{ + struct UHNode *found; + + found = uc_htable_next_bucket(node); + if (found == NULL) { + found = uc_htable_next_hlp(table, node->hash + 1); + } + + return found; +} + +void uc_htable_swap(struct UHTable *a, struct UHTable *b) +{ + struct UHTable tmp; + + tmp = *a; + *a = *b; + *b = tmp; +} + +int uc_htable_has_node(struct UHTable *table, struct UHNode *node) +{ + struct UHNode *iter; + + UC_HTABLE_FOREACH_BUCKET(table, iter, node->hash) { + if (node == iter) { + return 1; + } + } + + return 0; +} + +void uc_htable_clear(struct UHTable *table) +{ + memset(table->buckets, 0 , sizeof *table->buckets * table->cnt_buckets); + table->cnt_elements = 0; +} + +void uc_htable_insert(struct UHTable *table, struct UHNode *node, size_t hash) +{ + size_t bucket = uc_htable_bucket(table, hash); + + node->next = table->buckets[bucket]; + table->buckets[bucket] = node; + node->hash = hash; + + table->cnt_elements++; +} + +int uc_htable_resize(struct UHTable *table, size_t buckets) +{ + struct UHTable tmp_table; + int rc; + size_t h; + + if (buckets == 0) { + return EINVAL; + } + + rc = uc_htable_init(&tmp_table, buckets); + if (rc != 0) { + return rc; + } + + for (h = 0; h < table->cnt_buckets; h++) { + struct UHNode *next; + struct UHNode *node; + + for (node = uc_htable_first_bucket(table, h); + node != NULL; + node = next) { + next = node->next; + uc_htable_insert(&tmp_table, node, node->hash); + } + } + + uc_htable_swap(&tmp_table, table); + uc_htable_destroy(&tmp_table); + + return 0; +} + + diff --git a/test/test_runner.c b/test/test_runner.c index a21b4e9..3bd0a05 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -25,6 +25,7 @@ extern Suite *dbuf_suite(void); extern Suite *sprintb_suite(void); extern Suite *strv_suite(void); extern Suite *ratelimit_suite(void); +extern Suite *htable_suite(void); static suite_func suites[] = { bitvec_suite, @@ -44,6 +45,7 @@ static suite_func suites[] = { sprintb_suite, strv_suite, ratelimit_suite, + htable_suite, clock_suite, threadqueue_suite };