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_