From b4d76f79259dc87ec7b3b027adbd734694ba2d4f Mon Sep 17 00:00:00 2001 From: Vreixo Formoso Date: Sat, 12 Jan 2008 02:07:16 +0100 Subject: [PATCH] Add a hash table implementation. --- Makefile.am | 1 + src/util.h | 84 ++++++++++++++ src/util_htable.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_util.c | 72 +++++++++++- 4 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 src/util_htable.c diff --git a/Makefile.am b/Makefile.am index 339acc7..061f074 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,7 @@ src_libisofs_la_SOURCES = \ src/util.h \ src/util.c \ src/util_rbtree.c \ + src/util_htable.c \ src/filesrc.h \ src/filesrc.c \ src/ecma119.h \ diff --git a/src/util.h b/src/util.h index 7174255..2e39244 100644 --- a/src/util.h +++ b/src/util.h @@ -216,6 +216,11 @@ char *strcopy(const char *buf, size_t len); char *ucs2str(const char *buf, size_t len); typedef struct iso_rbtree IsoRBTree; +typedef struct iso_htable IsoHTable; + +typedef unsigned int (*hash_funtion_t)(const void *key); +typedef int (*compare_function_t)(const void *a, const void *b); +typedef void (*hfree_data_t)(void *key, void *data); /** * Create a new binary tree. libisofs binary trees allow you to add any data @@ -282,4 +287,83 @@ size_t iso_rbtree_get_size(IsoRBTree *tree); void **iso_rbtree_to_array(IsoRBTree *tree, int (*include_item)(void *), size_t *size); +/** + * Create a new hash table. + * + * @param size + * Number of slots in table. + * @param hash + * Function used to generate + */ +int iso_htable_create(size_t size, hash_funtion_t hash, + compare_function_t compare, IsoHTable **table); + +/** + * Put an element in a Hash Table. The element will be identified by + * the given key, that you should use to retrieve the element again. + * + * This function allow duplicates, i.e., two items with the same key. In those + * cases, the value returned by iso_htable_get() is undefined. If you don't + * want to allow duplicates, use iso_htable_put() instead; + * + * Both the key and data pointers will be stored internally, so you should + * free the objects they point to. Use iso_htable_remove() to delete an + * element from the table. + */ +int iso_htable_add(IsoHTable *table, void *key, void *data); + +/** + * Like iso_htable_add(), but this doesn't allow dulpicates. + * + * @return + * 1 success, 0 if an item with the same key already exists, < 0 error + */ +int iso_htable_put(IsoHTable *table, void *key, void *data); + +/** + * Retrieve an element from the given table. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param data + * Will be filled with the element found. Remains untouched if no + * element with the given key is found. + * @return + * 1 if found, 0 if not, < 0 on error + */ +int iso_htable_get(IsoHTable *table, void *key, void **data); + +/** + * Remove an item with the given key from the table. In tables that allow + * duplicates, it is undefined the element that will be deleted. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param free_data + * Function that will be called passing as parameters both the key and + * the element that will be deleted. The user can use it to free the + * element. You can pass NULL if you don't want to delete the item itself. + * @return + * 1 success, 0 no element exists with the given key, < 0 error + */ +int iso_htable_remove(IsoHTable *table, void *key, hfree_data_t free_data); + +/** + * Destroy the given hash table. + * + * Note that you're responsible to actually destroy the elements by providing + * a valid free_data function. You can pass NULL if you only want to delete + * the hash structure. + */ +void iso_htable_destroy(IsoHTable *table, hfree_data_t free_data); + +/** + * Hash function suitable for keys that are char strings. + */ +unsigned int iso_str_hash(const void *key); + #endif /*LIBISO_UTIL_H_*/ diff --git a/src/util_htable.c b/src/util_htable.c new file mode 100644 index 0000000..7471625 --- /dev/null +++ b/src/util_htable.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "util.h" +#include "error.h" + +#include +#include + +/* + * Hash table implementation + */ + +struct iso_hnode +{ + void *key; + void *data; + + /** next node for chaining */ + struct iso_hnode *next; +}; + +struct iso_htable +{ + struct iso_hnode **table; + + size_t size; /**< number of items in table */ + size_t cap; /**< number of slots in table */ + + hash_funtion_t hash; + compare_function_t compare; +}; + +static +struct iso_hnode *iso_hnode_new(void *key, void *data) +{ + struct iso_hnode *node = malloc(sizeof(struct iso_hnode)); + if (node == NULL) + return NULL; + + node->data = data; + node->key = key; + node->next = NULL; + return node; +} + +/** + * Put an element in a Hash Table. The element will be identified by + * the given key, that you should use to retrieve the element again. + * + * This function allow duplicates, i.e., two items with the same key. In those + * cases, the value returned by iso_htable_get() is undefined. If you don't + * want to allow duplicates, use iso_htable_put() instead; + * + * Both the key and data pointers will be stored internally, so you should + * free the objects they point to. Use iso_htable_remove() to delete an + * element from the table. + */ +int iso_htable_add(IsoHTable *table, void *key, void *data) +{ + struct iso_hnode *node; + struct iso_hnode *new; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + new = iso_hnode_new(key, data); + if (new == NULL) { + return ISO_MEM_ERROR; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + + table->size++; + new->next = node; + table->table[hash] = new; + return ISO_SUCCESS; +} + +/** + * Like iso_htable_add(), but this doesn't allow dulpicates. + * + * @return + * 1 success, 0 if an item with the same key already exists, < 0 error + */ +int iso_htable_put(IsoHTable *table, void *key, void *data) +{ + struct iso_hnode *node; + struct iso_hnode *new; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + + while (node) { + if (!table->compare(key, node->key)) { + return 0; + } + node = node->next; + } + + new = iso_hnode_new(key, data); + if (new == NULL) { + return ISO_MEM_ERROR; + } + + table->size++; + new->next = table->table[hash]; + table->table[hash] = new; + return ISO_SUCCESS; +} + +/** + * Retrieve an element from the given table. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param data + * Will be filled with the element found. Remains untouched if no + * element with the given key is found. + * @return + * 1 if found, 0 if not, < 0 on error + */ +int iso_htable_get(IsoHTable *table, void *key, void **data) +{ + struct iso_hnode *node; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + while (node) { + if (!table->compare(key, node->key)) { + if (data) { + *data = node->data; + } + return 1; + } + node = node->next; + } + return 0; +} + +/** + * Remove an item with the given key from the table. In tables that allow + * duplicates, it is undefined the element that will be deleted. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param free_data + * Function that will be called passing as parameters both the key and + * the element that will be deleted. The user can use it to free the + * element. You can pass NULL if you don't want to delete the item itself. + * @return + * 1 success, 0 no element exists with the given key, < 0 error + */ +int iso_htable_remove(IsoHTable *table, void *key, hfree_data_t free_data) +{ + struct iso_hnode *node, *prev; + unsigned int hash; + + if (table == NULL || key == NULL || free_data == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + prev = NULL; + while (node) { + if (!table->compare(key, node->key)) { + if (free_data) + free_data(key, node->data); + if (prev) { + prev->next = node->next; + } else { + table->table[hash] = node->next; + } + free(node); + table->size--; + return 1; + } + prev = node; + node = node->next; + } + return 0; +} + +/** + * Hash function suitable for keys that are char strings. + */ +unsigned int iso_str_hash(const void *key) +{ + int i, len; + const char *p = key; + unsigned int h = 2166136261u; + + len = strlen(p); + for (i = 0; i < len; i++) + h = (h * 16777619 ) ^ p[i]; + + return h; +} + +/** + * Destroy the given hash table. + * + * Note that you're responsible to actually destroy the elements by providing + * a valid free_data function. You can pass NULL if you only want to delete + * the hash structure. + */ +void iso_htable_destroy(IsoHTable *table, hfree_data_t free_data) +{ + size_t i; + struct iso_hnode *node, *tmp; + + if (table == NULL || free_data == NULL) { + return; + } + + for (i = 0; i < table->cap; ++i) { + node = table->table[i]; + while (node) { + tmp = node->next; + if (free_data) + free_data(node->key, node->data); + free(node); + } + } + free(table->table); + free(table); +} + +/** + * Create a new hash table. + * + * @param size + * Number of slots in table. + * @param hash + * Function used to generate + */ +int iso_htable_create(size_t size, hash_funtion_t hash, + compare_function_t compare, IsoHTable **table) +{ + IsoHTable *t; + + if (table == NULL) { + return ISO_MEM_ERROR; + } + + t = malloc(sizeof(IsoHTable)); + if (t == NULL) { + return ISO_MEM_ERROR; + } + t->table = calloc(size, sizeof(void*)); + if (t->table == NULL) { + free(t); + return ISO_MEM_ERROR; + } + t->cap = size; + t->size = 0; + t->hash = hash; + t->compare = compare; + + *table = t; + return ISO_SUCCESS; +} diff --git a/test/test_util.c b/test/test_util.c index 833d7f7..7fe32dd 100644 --- a/test/test_util.c +++ b/test/test_util.c @@ -256,7 +256,7 @@ static void test_iso_rbtree_insert() char *str1, *str2, *str3, *str4, *str5; void *str; - res = iso_rbtree_new(strcmp, &tree); + res = iso_rbtree_new((compare_function_t)strcmp, &tree); CU_ASSERT_EQUAL(res, 1); /* ok, insert one str */ @@ -300,6 +300,75 @@ static void test_iso_rbtree_insert() iso_rbtree_destroy(tree, NULL); } +void test_iso_htable_put_get() +{ + int res; + IsoHTable *table; + char *str1, *str2, *str3, *str4, *str5; + void *str; + + res = iso_htable_create(4, iso_str_hash, (compare_function_t)strcmp, &table); + CU_ASSERT_EQUAL(res, 1); + + /* try to get str from empty table */ + res = iso_htable_get(table, "first str", &str); + CU_ASSERT_EQUAL(res, 0); + + /* ok, insert one str */ + str1 = "first str"; + res = iso_htable_put(table, str1, str1); + CU_ASSERT_EQUAL(res, 1); + + /* and now get str from table */ + res = iso_htable_get(table, "first str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str1); + res = iso_htable_get(table, "second str", &str); + CU_ASSERT_EQUAL(res, 0); + + str2 = "second str"; + res = iso_htable_put(table, str2, str2); + CU_ASSERT_EQUAL(res, 1); + + str = NULL; + res = iso_htable_get(table, "first str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str1); + res = iso_htable_get(table, "second str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str2); + + /* insert again, with same key but other data */ + res = iso_htable_put(table, str2, str1); + CU_ASSERT_EQUAL(res, 0); + + res = iso_htable_get(table, "second str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str2); + + str3 = "third str"; + res = iso_htable_put(table, str3, str3); + CU_ASSERT_EQUAL(res, 1); + + str4 = "four str"; + res = iso_htable_put(table, str4, str4); + CU_ASSERT_EQUAL(res, 1); + + str5 = "fifth str"; + res = iso_htable_put(table, str5, str5); + CU_ASSERT_EQUAL(res, 1); + + /* some searches */ + res = iso_htable_get(table, "sixth str", &str); + CU_ASSERT_EQUAL(res, 0); + + res = iso_htable_get(table, "fifth str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str5); + + iso_htable_destroy(table, NULL); +} + void add_util_suite() { CU_pSuite pSuite = CU_add_suite("UtilSuite", NULL, NULL); @@ -316,4 +385,5 @@ void add_util_suite() CU_add_test(pSuite, "iso_1_fileid()", test_iso_1_fileid); CU_add_test(pSuite, "iso_2_fileid()", test_iso_2_fileid); CU_add_test(pSuite, "iso_rbtree_insert()", test_iso_rbtree_insert); + CU_add_test(pSuite, "iso_htable_put/get()", test_iso_htable_put_get); }