diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26578e4..2bb27a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: push: jobs: - tests: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 09484e8..976f52b 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,5 @@ dkms.conf build .direnv -result \ No newline at end of file +result +/debug.c diff --git a/README.md b/README.md index abd8153..3f7d2b9 100644 --- a/README.md +++ b/README.md @@ -68,31 +68,49 @@ uint32_t custom_hash_function(uint8_t *bytes, size_t size) int main(int argc, const char* argv[]) { th_table_t table; + bool success; + Person person = { "James", true }; // Initialize the table th_table_init(&table); // Insert a new key value pair - th_table_put(&table, "key_1", strlen("key_1"), &person); + success = th_table_put(&table, "key_1", strlen("key_1"), &person); + if (success == false) { + fprintf(stderr, "Unable to insert\n"); + return 1; + } // Get the last inserted value Person *james; james = th_table_get(&table, "key_1", strlen("key_1")); - if (james == NULL) { - fprintf(stderr, "Unable to get value from key_1"); + + success = james != NULL; + if (success == false) { + fprintf(stderr, "It does not exist\n"); return 1; } printf("name -> %s, is_hydrated -> %d\n", james->name, james->is_hydrated); - // Feel free to change the hash function - table.options.hash_func = custom_hash_function; + // Delete the entry + success = th_table_delete(&table, "key_1", strlen("key_1")); + if (success == false) { + fprintf(stderr, "Unable to delete\n"); + return 1; + } + + // Verify that it doesnt exist anymore + james = th_table_get(&table, "key_1", strlen("key_1")); + if (james != NULL) { + fprintf(stderr, "The entry still exists\n"); + return 1; + } // Free the allocated memory - th_table_destroy(&table); + th_table_free(&table); return 0; } - ``` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1711f1f..fe14a49 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ set ( hash.c entry.c table.c + key.c ) set ( @@ -20,7 +21,8 @@ set ( entry.h table.h hash.h - options.h + types.h + key.h ) add_library (${TINYHASH_NAME} ${TINYHASH_LIB_TYPE} ${TINYHASH_SRC}) diff --git a/src/entry.c b/src/entry.c index 9dd95e5..bab96f2 100644 --- a/src/entry.c +++ b/src/entry.c @@ -1,40 +1,36 @@ #include "entry.h" -#include "hash.h" - -#include static th_entry_t *th_entry_new(th_key_t *key, th_any_t value) { th_entry_t *entry = malloc(sizeof(th_entry_t)); + if (entry == NULL) return NULL; + entry->key = *key; entry->value = value; + entry->previous = NULL; entry->next = NULL; return entry; } -static void th_entry_raw_add(th_entry_t **root, - th_entry_t *entry) +static void th_entry_raw_add(th_entry_t **root, th_entry_t *entry) { + if (*root != NULL) { + (*root)->previous = entry; + } + entry->next = *root; *root = entry; } -void th_entry_add(th_entry_t **root, th_key_t *key, - th_any_t value) +bool th_entry_add(th_entry_t **root, th_key_t *key, th_any_t value) { th_entry_t *entry = th_entry_new(key, value); + + if (entry == NULL) return false; th_entry_raw_add(root, entry); -} -th_key_t th_key_new(th_any_t data, size_t size, - th_hash_func_t hash_func) -{ - return (th_key_t) { - .hash = hash_func(data, size), - .size = size, - .data = data, - }; + return true; } diff --git a/src/entry.h b/src/entry.h index d0191f9..baac018 100644 --- a/src/entry.h +++ b/src/entry.h @@ -3,28 +3,19 @@ #include #include +#include #include "hash.h" - -typedef void *th_any_t; - -typedef struct { - uint32_t hash; - int size; - th_any_t data; -} th_key_t; +#include "key.h" typedef struct th_entry_s { th_key_t key; th_any_t value; + struct th_entry_s *previous; struct th_entry_s *next; } th_entry_t; -void th_entry_add(th_entry_t **root, th_key_t *key, - th_any_t value); - -th_key_t th_key_new(th_any_t data, size_t size, - th_hash_func_t hash_func); +bool th_entry_add(th_entry_t **root, th_key_t *key, th_any_t value); #endif diff --git a/src/hash.c b/src/hash.c index 2236234..6c73668 100644 --- a/src/hash.c +++ b/src/hash.c @@ -1,18 +1,18 @@ #include #include -#define HASH_INITIAL_VALUE 2166136261u -#define HASH_MUL_VALUE 16777619 +#define TH_HASH_INITIAL_VALUE 2166136261u +#define TH_HASH_MUL_VALUE 16777619 uint32_t th_hash(uint8_t *bytes, size_t size) { if (bytes == NULL) return 0; - uint32_t hash = HASH_INITIAL_VALUE; + uint32_t hash = TH_HASH_INITIAL_VALUE; for (int i = 0; i < size; i++) { hash ^= bytes[i]; - hash *= HASH_MUL_VALUE; + hash *= TH_HASH_MUL_VALUE; } return hash; diff --git a/src/hash.h b/src/hash.h index 37a14f6..e7753e6 100644 --- a/src/hash.h +++ b/src/hash.h @@ -4,8 +4,6 @@ #include #include -typedef uint32_t (*th_hash_func_t)(uint8_t *bytes, size_t size); - uint32_t th_hash(uint8_t *bytes, size_t size); #endif diff --git a/src/key.c b/src/key.c new file mode 100644 index 0000000..1cd796b --- /dev/null +++ b/src/key.c @@ -0,0 +1,20 @@ +#include + +#include "key.h" +#include "hash.h" + +th_key_t th_key_new(th_any_t data, size_t size) +{ + return (th_key_t) { + .hash = th_hash(data, size), + .size = size, + .data = data, + }; +} + +bool th_key_is_equal(th_key_t *first, th_key_t *second) +{ + if (first->size != second->size) return false; + + return memcmp(first->data, second->data, first->size) == 0; +} diff --git a/src/key.h b/src/key.h new file mode 100644 index 0000000..6d4b558 --- /dev/null +++ b/src/key.h @@ -0,0 +1,20 @@ +#ifndef __TINYHASH_KEY_H__ +#define __TINYHASH_KEY_H__ + +#include +#include +#include + +#include "types.h" + +typedef struct { + uint32_t hash; + int size; + th_any_t data; +} th_key_t; + +th_key_t th_key_new(th_any_t data, size_t size); + +bool th_key_is_equal(th_key_t *first, th_key_t *second); + +#endif diff --git a/src/options.h b/src/options.h deleted file mode 100644 index 1001c72..0000000 --- a/src/options.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef __TINYHASH_OPTIONS_H__ -#define __TINYHASH_OPTIONS_H__ - -#include - -#include "hash.h" - -typedef struct { - th_hash_func_t hash_func; -} th_options_t; - -#endif diff --git a/src/table.c b/src/table.c index b5da0e3..ad31f11 100644 --- a/src/table.c +++ b/src/table.c @@ -5,10 +5,8 @@ #include "table.h" #include "entry.h" -#include "hash.h" -#include "options.h" -void static th_table_put_with_key(th_table_t *table, th_key_t *key, +static bool th_table_put_with_key(th_table_t *table, th_key_t *key, th_any_t value); void th_table_init(th_table_t *table) @@ -16,53 +14,50 @@ void th_table_init(th_table_t *table) table->capacity = 0; table->count = 0; table->entries = NULL; - - table->options = (th_options_t) { - .hash_func = th_hash - }; } -static void th_table_increase(th_table_t *table) +static bool th_table_increase(th_table_t *table) { th_table_t new_table; th_table_init(&new_table); // New capacity - new_table.capacity = TABLE_NEXT_CAPACITY(table->capacity); + new_table.capacity = TH_TABLE_NEXT_CAPACITY(table->capacity); // New entry array bytes size size_t size = sizeof(th_entry_t *) * new_table.capacity; new_table.entries = (th_entry_t **) malloc(size); + + if (new_table.entries == NULL) return false; + memset(new_table.entries, 0, size); // Re-compute the new index for (int i = 0; i < table->capacity; i++) { + bool success; th_entry_t *entry = table->entries[i]; while (entry != NULL) { - th_table_put_with_key( + success = th_table_put_with_key( &new_table, &entry->key, entry->value ); + if (success == false) return false; + entry = entry->next; } } // Destroy the old table - th_table_destroy(table); + th_table_free(table); *table = new_table; -} -static bool th_is_key_equal(th_key_t *first, th_key_t *second) -{ - if (first->size != second->size) return false; - - return memcmp(first->data, second->data, first->size) == 0; + return true; } static th_entry_t *th_table_find(th_table_t *table, th_key_t *key) @@ -74,7 +69,7 @@ static th_entry_t *th_table_find(th_table_t *table, th_key_t *key) th_entry_t *entry = table->entries[index]; while (entry != NULL) { - if (th_is_key_equal(key, &entry->key)) { + if (th_key_is_equal(key, &entry->key)) { return entry; } @@ -86,7 +81,7 @@ static th_entry_t *th_table_find(th_table_t *table, th_key_t *key) th_any_t th_table_get(th_table_t *table, th_any_t data, size_t key_data_size) { - th_key_t key = th_key_new(data, key_data_size, table->options.hash_func); + th_key_t key = th_key_new(data, key_data_size); th_entry_t *entry = th_table_find(table, &key); if (entry == NULL) return NULL; @@ -94,34 +89,68 @@ th_any_t th_table_get(th_table_t *table, th_any_t data, size_t key_data_size) return entry->value; } -void static th_table_put_with_key(th_table_t *table, th_key_t *key, +static bool th_table_put_with_key(th_table_t *table, th_key_t *key, th_any_t value) { if (table->count >= table->capacity) { - th_table_increase(table); + bool success = th_table_increase(table); + if (success == false) return false; } th_entry_t *entry = th_table_find(table, key); if (entry == NULL) { int index = key->hash % table->capacity; - th_entry_add(&table->entries[index], key, value); + th_entry_t **bucket = &table->entries[index]; + + if (*bucket == NULL) table->count++; - table->count++; + bool success = th_entry_add(bucket, key, value); + if (success == false) return false; } else { entry->value = value; } + + return true; } -void th_table_put(th_table_t *table, th_any_t data, size_t key_data_size, +bool th_table_put(th_table_t *table, th_any_t data, size_t key_data_size, th_any_t value) { - th_key_t key = th_key_new(data, key_data_size, table->options.hash_func); + th_key_t key = th_key_new(data, key_data_size); + + return th_table_put_with_key(table, &key, value); +} + +bool th_table_delete(th_table_t *table, th_any_t data, size_t key_data_size) +{ + th_key_t key = th_key_new(data, key_data_size); + th_entry_t *entry = th_table_find(table, &key); + + if (entry == NULL) return false; + + int index = entry->key.hash % table->capacity; + th_entry_t **bucket = &table->entries[index]; + + if (entry->next == NULL && entry->previous == NULL) { + *bucket = NULL; + table->count--; + } else if (entry->previous == NULL) { + *bucket = entry->next; + (*bucket)->previous = NULL; + } else if (entry->next == NULL) { + entry->previous->next = entry->next; + } else { + entry->previous->next = entry->next; + entry->next->previous = entry->previous; + } + + free(entry); - th_table_put_with_key(table, &key, value); + return true; } -void th_table_destroy(th_table_t *table) +void th_table_free(th_table_t *table) { th_entry_t *entry; th_entry_t *previous; diff --git a/src/table.h b/src/table.h index 7b7a712..cc2acbc 100644 --- a/src/table.h +++ b/src/table.h @@ -6,25 +6,25 @@ #include #include "entry.h" -#include "options.h" typedef struct { uint32_t count; uint32_t capacity; th_entry_t **entries; - th_options_t options; } th_table_t; -#define TABLE_NEXT_CAPACITY(capacity) \ +#define TH_TABLE_NEXT_CAPACITY(capacity) \ (capacity) == 0 ? 8 : (capacity) * 2 void th_table_init(th_table_t *table); th_any_t th_table_get(th_table_t *table, th_any_t data, size_t key_data_size); -void th_table_put(th_table_t *table, th_any_t data, size_t key_data_size, +bool th_table_put(th_table_t *table, th_any_t data, size_t key_data_size, th_any_t value); -void th_table_destroy(th_table_t *table); +void th_table_free(th_table_t *table); + +bool th_table_delete(th_table_t *table, th_any_t data, size_t key_data_size); #endif diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..834c821 --- /dev/null +++ b/src/types.h @@ -0,0 +1,6 @@ +#ifndef __TINYHASH_TYPES_H__ +#define __TINYHASH_TYPES_H__ + +typedef void *th_any_t; + +#endif diff --git a/tests/test_table.c b/tests/test_table.c index 0adcf3f..34bc7af 100644 --- a/tests/test_table.c +++ b/tests/test_table.c @@ -24,7 +24,7 @@ MunitResult test_th_table_put_and_get(const MunitParameter params[], void* data) munit_assert_uint64((uint64_t) value, ==, 333); - th_table_destroy(&table); + th_table_free(&table); return MUNIT_OK; } @@ -38,7 +38,7 @@ MunitResult test_th_table_get_with_empty_table(const MunitParameter params[], vo value = th_table_get(&table, "hello", strlen("hello")); - th_table_destroy(&table); + th_table_free(&table); return MUNIT_OK; } @@ -57,9 +57,9 @@ MunitResult test_th_table_put_with_full_table(const MunitParameter params[], voi th_table_put(&table, j, sizeof(i), j); } - for (int i = 0; i < SET_GET_ITERATIONS; i++) { value = (int *) th_table_get(&table, &i, sizeof(i)); + th_table_delete(&table, &i, sizeof(i)); munit_assert_int(*value, ==, i); @@ -73,7 +73,8 @@ MunitResult test_th_table_put_with_full_table(const MunitParameter params[], voi th_table_put(&table, "f", strlen("f"), (void *) 1); th_table_put(&table, "g", strlen("g"), (void *) 1); th_table_put(&table, "h", strlen("h"), (void *) 1); - + th_table_put(&table, "hva", strlen("hva"), (void *) 1); + th_table_put(&table, "hb", strlen("hb"), (void *) 1); th_table_put(&table, "azdaz", strlen("azdaz"), (void *) 10); @@ -81,7 +82,7 @@ MunitResult test_th_table_put_with_full_table(const MunitParameter params[], voi munit_assert_int((uint64_t) value, ==, 10); - th_table_destroy(&table); + th_table_free(&table); return MUNIT_OK; } @@ -101,7 +102,7 @@ MunitResult test_th_table_put_overwrite(const MunitParameter params[], void* dat munit_assert_uint64((uint64_t) value, ==, 2); - th_table_destroy(&table); + th_table_free(&table); return MUNIT_OK; } @@ -122,7 +123,7 @@ MunitResult test_th_table_put_collision(const MunitParameter params[], void* dat value = th_table_get(&table, "ello", strlen("ello")); munit_assert_uint64((uint64_t) value, ==, 456); - th_table_destroy(&table); + th_table_free(&table); return MUNIT_OK; } @@ -146,7 +147,71 @@ MunitResult test_th_table_put_struct_as_key(const MunitParameter params[], void* munit_assert_uint8(test_struct.b[3], ==, 222); - th_table_destroy(&table); + th_table_free(&table); + + return MUNIT_OK; +} + +MunitResult test_th_table_delete(const MunitParameter params[], void* data) +{ + th_table_t table; + th_any_t value; + bool ok; + + th_table_init(&table); + + char *keys[] = { + "a", + "b", + "bb", + "bb", + "bbb", + "c", + "ello" + }; + + size_t keys_length = sizeof(keys) / sizeof(keys[0]); + + // Insert key value pair + for (int i = 0; i < keys_length; i++) { + th_table_put(&table, keys[i], strlen(keys[i]), (void *) (uint64_t) i + 1); + } + + // Check everything exists + for (int i = 0; i < keys_length; i++) { + value = th_table_get(&table, keys[i], strlen(keys[i])); + munit_assert_not_null(value); + } + + ok = th_table_delete(&table, "a", strlen("a")); + munit_assert_true(ok); + + ok = th_table_delete(&table, "a", strlen("a")); + munit_assert_false(ok); + + ok = th_table_delete(&table, "ello", strlen("ello")); + munit_assert_true(ok); + ok = th_table_delete(&table, "bb", strlen("bb")); + munit_assert_true(ok); + ok = th_table_delete(&table, "b", strlen("b")); + munit_assert_true(ok); + ok = th_table_delete(&table, "bbb", strlen("bbb")); + munit_assert_true(ok); + + value = th_table_get(&table, "a", strlen("a")); + munit_assert_null(value); + + // Delete everything + for (int i = 0; i < keys_length; i++) { + th_table_delete(&table, keys[i], strlen(keys[i])); + + value = th_table_get(&table, keys[i], strlen(keys[i])); + munit_assert_null(value); + } + + munit_assert_uint32(table.count, ==, 0); + + th_table_free(&table); return MUNIT_OK; } diff --git a/tests/test_table.h b/tests/test_table.h index 0fa025b..19563c3 100644 --- a/tests/test_table.h +++ b/tests/test_table.h @@ -9,5 +9,6 @@ MunitResult test_th_table_put_with_full_table(const MunitParameter params[], voi MunitResult test_th_table_put_overwrite(const MunitParameter params[], void* data); MunitResult test_th_table_put_collision(const MunitParameter params[], void* data); MunitResult test_th_table_put_struct_as_key(const MunitParameter params[], void* data); +MunitResult test_th_table_delete(const MunitParameter params[], void* data); #endif diff --git a/tests/tests.c b/tests/tests.c index 60fc189..b84c735 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -5,7 +5,7 @@ static MunitTest test_th_suite_tests[] = { { - "/hash_str", + "/th_hash_str", test_th_hash, NULL, NULL, @@ -13,7 +13,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/hash_str_multiple_iteration", + "/th_hash_str_multiple_iteration", test_th_hash_multiple_iteration, NULL, NULL, @@ -21,7 +21,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/hash_str_null", + "/th_hash_str_null", test_th_hash_null, NULL, NULL, @@ -29,7 +29,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/hash_str_with_int", + "/th_hash_str_with_int", test_th_hash_with_int, NULL, NULL, @@ -37,7 +37,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/table_set_and_get", + "/th_table_set_and_get", test_th_table_put_and_get, NULL, NULL, @@ -45,7 +45,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/table_get_with_empty_table", + "/th_table_get_with_empty_table", test_th_table_get_with_empty_table, NULL, NULL, @@ -53,7 +53,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/table_set_with_full_table", + "/th_table_set_with_full_table", test_th_table_put_with_full_table, NULL, NULL, @@ -61,7 +61,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/table_set_overwrite", + "/th_table_set_overwrite", test_th_table_put_overwrite, NULL, NULL, @@ -69,7 +69,7 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/table_set_collision", + "/th_table_set_collision", test_th_table_put_collision, NULL, NULL, @@ -77,13 +77,21 @@ static MunitTest test_th_suite_tests[] = { NULL }, { - "/table_put_struct_as_key", + "/th_table_put_struct_as_key", test_th_table_put_struct_as_key, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, + { + "/th_table_delete", + test_th_table_delete, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };