From 86eb3aee019d77251c4fd514690f09d60e70b015 Mon Sep 17 00:00:00 2001 From: Linus Probert Date: Tue, 12 Dec 2017 13:06:01 +0100 Subject: [PATCH] Wrote a naive hashtable. Be careful regarding memeory handling using this table. It takes pointers as values. Deletes the pointers on occasion and destruction but doesn't copy the original pointer. So pointers inserted should be left to be handled by the table. --- CMakeLists.txt | 2 + hashtable/CMakeLists.txt | 15 ++++ hashtable/check_hashtable.c | 92 ++++++++++++++++++++++ hashtable/hashtable.c | 151 ++++++++++++++++++++++++++++++++++++ hashtable/hashtable.h | 23 ++++++ 5 files changed, 283 insertions(+) create mode 100644 hashtable/CMakeLists.txt create mode 100644 hashtable/check_hashtable.c create mode 100644 hashtable/hashtable.c create mode 100644 hashtable/hashtable.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1726d7b..44d611d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ project(breakhack C) include(FindLua) add_subdirectory(linkedlist) +add_subdirectory(hashtable) include_directories(linkedlist ${LUA_INCLUDE_DIR} @@ -32,6 +33,7 @@ add_executable(breakhack target_link_libraries(breakhack linkedlist + hashtable ${LUA_LIBRARIES} -lSDL2 -lSDL2_image diff --git a/hashtable/CMakeLists.txt b/hashtable/CMakeLists.txt new file mode 100644 index 0000000..3426dbd --- /dev/null +++ b/hashtable/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required (VERSION 3.2.0) + +SET(CMAKE_COLOR_MAKEFILE ON) + +project(hashtable C) + +add_definitions("-Wall") + +add_library(hashtable hashtable.c) + +enable_testing() +add_executable(test_table EXCLUDE_FROM_ALL check_hashtable hashtable) +target_compile_options(test_table PRIVATE -pthread) +target_link_libraries(test_table -lcheck) +add_test(test_table test_table) diff --git a/hashtable/check_hashtable.c b/hashtable/check_hashtable.c new file mode 100644 index 0000000..de82df7 --- /dev/null +++ b/hashtable/check_hashtable.c @@ -0,0 +1,92 @@ +#include +#include +#include "hashtable.h" + +START_TEST(test_hashtable_create) +{ + Hashtable *table = ht_create(10); + ck_assert( table->size == 10 ); + ck_assert( table != NULL ); + ht_destroy(table); +} +END_TEST + +START_TEST(test_hashtable_set_get) +{ + int i; + int* values0[10]; + int* values1[10]; + char* keys[] = { + "key0", + "key1", + "key2", + "key3", + "key4", + "key5", + "key6", + "key7", + "key8", + "key9", + }; + + for (i = 0; i < 10; ++i) { + values0[i] = malloc(sizeof(int)); + *values0[i] = i; + + values1[i] = malloc(sizeof(int)); + *values1[i] = 9-i; + } + + Hashtable *table = ht_create(10); + + for (i = 0; i < 10; ++i) { + ht_set(table, keys[i], values0[i]); + } + + for (i = 0; i < 10; ++i) { + ck_assert( *values0[i] == *((int*) ht_get(table, keys[i])) ); + } + + for (i = 0; i < 10; ++i) { + ht_set(table, keys[i], values1[i]); + } + + for (i = 0; i < 10; ++i) { + ck_assert( *values1[i] == *((int*) ht_get(table, keys[i])) ); + } + + ht_destroy(table); +} +END_TEST + +Suite* t_suite_create() +{ + Suite *s; + TCase *tc_core; + + s = suite_create("Hashtable"); + tc_core = tcase_create("Core"); + + tcase_add_test(tc_core, test_hashtable_create); + tcase_add_test(tc_core, test_hashtable_set_get); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) +{ + int number_failed; + Suite *s; + SRunner *sr; + + s = t_suite_create(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + + srunner_free(sr); + + return number_failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/hashtable/hashtable.c b/hashtable/hashtable.c new file mode 100644 index 0000000..f3d7370 --- /dev/null +++ b/hashtable/hashtable.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include "hashtable.h" + +static void* +ec_malloc(unsigned int size) +{ + void *ptr = malloc(size); + if (ptr == NULL) { + fprintf(stderr, "[!!] Failed to allocate hashtable\n"); + exit(-1); + } + + return ptr; +} + + +Hashtable* +ht_create(unsigned int size) +{ + Hashtable *table; + unsigned int i; + + if (size < 1) + return NULL; + + table = ec_malloc(sizeof(Hashtable)); + table->size = size; + table->entries = ec_malloc(sizeof(Entry) * size); + + for (i = 0; i < size; ++i) { + table->entries[i] = NULL; + } + + return table; +} + +static unsigned int +hash(Hashtable *table, char *key) +{ + unsigned long int hashval = 0; + int i = 0; + + while (hashval < ULONG_MAX && i < strlen(key)) { + hashval = hashval << 8; + hashval += key[i++]; + } + + return hashval % table->size; +} + +static Entry* +entry_create(char *key, void *value) +{ + Entry *entry; + + entry = ec_malloc(sizeof(Entry)); + entry->key = strdup(key); + entry->value = value; + entry->next = NULL; + + return entry; +} + +void +ht_set(Hashtable *table, char *key, void *val) +{ + int hashkey = 0; + Entry *newEntry; + Entry *next; + Entry *last; + + hashkey = hash(table, key); + + next = table->entries[hashkey]; + + /* Find a position */ + while (next != NULL + && next->key != NULL + && strcmp(key, next->key) > 0) + { + last = next; + next = next->next; + } + + if (next && next->key && strcmp(key, next->key) == 0) { + /* Collision */ + free(next->value); + next->value = val; + } else { + /* New entry */ + newEntry = entry_create(key, val); + + if (next == table->entries[hashkey]) { + table->entries[hashkey] = newEntry; + newEntry->next = next; + } else if(next == NULL) { + last->next = newEntry; + } else { + newEntry->next = next; + last->next = newEntry; + } + } +} + +void* +ht_get(Hashtable *table, char *key) +{ + int hashkey = 0; + Entry *entry; + + hashkey = hash(table, key); + + entry = table->entries[hashkey]; + + while (entry && entry->key && strcmp(entry->key, key) > 0) { + entry = entry->next; + } + + if (!entry || !entry->key || strcmp(entry->key, key) != 0) { + return NULL; + } + return entry->value; +} + +void +ht_destroy(Hashtable *table) +{ + Entry *entry, *next; + unsigned int i; + + if (table == NULL) { + return; + } + + for (i = 0; i < table->size; ++i) { + entry = table->entries[i]; + if (entry == NULL) + continue; + while (entry) { + next = entry->next; + free(entry->value); + entry->value = NULL; + free(entry); + entry = next; + } + } + free(table); +} diff --git a/hashtable/hashtable.h b/hashtable/hashtable.h new file mode 100644 index 0000000..731244a --- /dev/null +++ b/hashtable/hashtable.h @@ -0,0 +1,23 @@ +#ifndef HASHTABLE_H_ +#define HASHTABLE_H_ + +typedef struct entry_t { + char *key; + void *value; + struct entry_t *next; +} Entry; + +typedef struct table_t { + unsigned int size; + Entry **entries; +} Hashtable; + +Hashtable* ht_create(unsigned int size); + +void ht_set(Hashtable*, char *key, void *val); + +void* ht_get(Hashtable*, char *key); + +void ht_destroy(Hashtable*); + +#endif // HASHTABLE_H_