From fb4a9a8127b4e105b23ff0595d584e0c0c6d4b3a Mon Sep 17 00:00:00 2001 From: Randy Jordan Date: Mon, 14 Jul 2025 19:04:06 -0500 Subject: [PATCH] Initial commit --- LICENSE | 21 ++++++ Makefile | 76 +++++++++++++++++++ README.md | 32 ++++++++ include/except.h | 80 ++++++++++++++++++++ include/hash_table.h | 30 ++++++++ src/except.c | 30 ++++++++ src/hash_table.c | 172 +++++++++++++++++++++++++++++++++++++++++++ tests/01_table.c | 54 ++++++++++++++ 8 files changed, 495 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/except.h create mode 100644 include/hash_table.h create mode 100644 src/except.c create mode 100644 src/hash_table.c create mode 100644 tests/01_table.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..95a2a59 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Compiler Flags +CC := gcc +CFLAGS := -g -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined -fno-omit-frame-pointer + +# Directory variables +LIBDIR := lib +OBJ := obj +INC := include +SRC := src +TEST := tests + +# Filepath Pattern Matching +LIB := $(LIBDIR)/lib.a +SRCS := $(wildcard $(SRC)/*.c) +OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS)) +TESTS := $(wildcard $(TEST)/*.c) +TESTBINS := $(patsubst $(TEST)/%.c, $(TEST)/bin/%, $(TESTS)) + +# Commands must be labeled PHONY +.PHONY: all release clean test + +# Compiler Release Flags +release: CFLAGS := -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined -fno-omit-frame-pointer -O2 -DNDEBUG +release: clean $(LIB) + +# Target for compilation. +all: $(LIB) + +# Target / Dependencies +$(LIB): $(OBJS) | $(LIBDIR) + $(RM) $(LIB) + ar -cvrs $@ $^ + +$(OBJ)/%.o: $(SRC)/%.c $(SRC)/%.h | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +$(TEST)/bin/%: $(TEST)/%.c $(LIB) | $(TEST)/bin + $(CC) $(CFLAGS) $< $(LIB) -o $@ + +# Make directories if none. +$(LIBDIR): + mkdir $@ + +$(INC): + mkdir $@ + +$(OBJ): + mkdir $@ + +$(TEST)/bin: + mkdir $@ + +# Run the tests in the bin folder and track results +test: $(LIB) $(TEST)/bin $(TESTBINS) + @SUCCESS_COUNT=0; FAILURE_COUNT=0; \ + for test in $(TESTBINS); do \ + ./$$test; \ + EXIT_CODE=$$?; \ + TEST_NAME=$(notdir $$test); \ + if [ $$EXIT_CODE -eq 0 ]; then \ + echo "\033[0;32m$$TEST_NAME: EXIT CODE: $$EXIT_CODE (SUCCESS)\033[0m"; \ + SUCCESS_COUNT=$$((SUCCESS_COUNT + 1)); \ + else \ + echo "\033[0;31m$$TEST_NAME: EXIT CODE: $$EXIT_CODE (FAILURE)\033[0m"; \ + FAILURE_COUNT=$$((FAILURE_COUNT + 1)); \ + fi; \ + done; \ + echo "\n\nTests completed"; \ + echo "SUCCESS: $$SUCCESS_COUNT"; \ + echo "FAILURE: $$FAILURE_COUNT"; + +clean: + $(RM) -r $(LIBDIR) $(OBJ) $(TEST)/bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0c5da3 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Hash Table + +## Description +Hash Table implementation written in C. + +## Table of Contents + +- [Description](#description) +- [Features](#features) +- [Usage](#usage) +- [Credits / Resources](#credits--resources) +- [License](#license) + +## Features / TODOS + +- [x] Custom cleanup function +- [x] General purpose void * values. +- [x] O(1) lookup +- [x] Variety of hash functions. + +## Usage +make to build
+make test for testing. + +## Credits / Resources +[Data Structures in C - Noel Kalicharan]
+[C Interfaces and Implmentations](https://github.com/drh/cii)
+[Jacob Sorber](https://www.youtube.com/@JacobSorber)
+ + +## License +This project is licensed under MIT - see the [LICENSE](LICENSE) file for details. diff --git a/include/except.h b/include/except.h new file mode 100644 index 0000000..3ccbf3a --- /dev/null +++ b/include/except.h @@ -0,0 +1,80 @@ +#ifndef EXCEPT_INCLUDED +#define EXCEPT_INCLUDED + +#include + +struct Exception { + const char *reason; +}; +typedef struct Exception Exception; + +struct ExceptFrame { + struct ExceptFrame *prev; // Exception Stack + jmp_buf env; // Enviroment Buffer + const char *file; // Exception File + int line; // Exception Line + const Exception *exception; // Exception Reason +}; +typedef struct ExceptFrame ExceptFrame; + +// Exception States +enum { EXCEPT_ENTERED=0, EXCEPT_RAISED, EXCEPT_HANDLED, EXCEPT_FINALIZED}; + +void except_raise(const Exception *e, const char *file,int line); // Raise exceptions + +// External declarations +extern ExceptFrame *except_stack; // Global exception stack +extern const Exception assert_failed; // Forward declaration for assert. +extern void asserted(int e); + +#ifdef NDEBUG +#define ASSERTED(e) ((void)0) +#else +#define ASSERTED(e) ((void)((e)||(RAISE(assert_failed),0))) +#endif + +// Raise an Exception. +#define RAISE(e) except_raise(&(e), __FILE__, __LINE__) + +// Reraise the currect exception. +#define RERAISE except_raise(except_frame.exception, \ + except_frame.file, except_frame.line) + +// Switch to the previous exception frame and return. +#define RETURN switch (except_stack = except_stack->prev,0) default: return + +// Start a try block. +#define TRY do { \ + volatile int except_flag; \ + ExceptFrame except_frame; \ + except_frame.prev = except_stack; \ + except_stack = &except_frame; \ + except_flag = setjmp(except_frame.env); \ + if (except_flag == EXCEPT_ENTERED) { + +// Handle specific example. +#define EXCEPT(e) \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } else if (except_frame.exception == &(e)) { \ + except_flag = EXCEPT_HANDLED; + +// Catch all other exceptions. +#define ELSE \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } else { \ + except_flag = EXCEPT_HANDLED; + +// Execute finalization code. +#define FINALLY \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } { \ + if (except_flag == EXCEPT_ENTERED) \ + except_flag = EXCEPT_FINALIZED; + +// End Try block. +#define END_TRY \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } if (except_flag == EXCEPT_RAISED) RERAISE; \ +} while (0) + +#endif diff --git a/include/hash_table.h b/include/hash_table.h new file mode 100644 index 0000000..2f35ca7 --- /dev/null +++ b/include/hash_table.h @@ -0,0 +1,30 @@ +#ifndef HT_INCLUDED +#define HT_INCLUDED + +#include +#include + +#define FNV_PRIME 0x100000001b3 +#define FNV_OFFSET 0xcbf29ce48422325UL + +typedef enum { + SOFTFAIL = 0x1, +} TableFlags; + +typedef uint64_t (hash_func) (const char *, size_t); +typedef void (cleanup_func) (void *ptr); +typedef struct Entry Entry; +typedef struct Hash_Table Hash_Table; + +Hash_Table* hash_table_new(uint32_t size, hash_func *hf, cleanup_func *cf, int flags); +void hash_table_free(Hash_Table *ht); +void hash_table_print(Hash_Table *ht, int printNull); +uint64_t hash_table_create(Hash_Table *ht, const char *key, void *obj, int flags); +void* hash_table_read(Hash_Table *ht, const char *key); +void* hash_table_delete(Hash_Table *ht, const char *key); + +uint64_t hash_fnv0(const char *buf, size_t len); +uint64_t hash_fnv1(const char *buf, size_t len); +uint64_t hash_fnv1a(const char *buf, size_t len); + +#endif diff --git a/src/except.c b/src/except.c new file mode 100644 index 0000000..6d3c652 --- /dev/null +++ b/src/except.c @@ -0,0 +1,30 @@ +#include "../include/except.h" +#include +#include + +ExceptFrame *except_stack = NULL; // Global exception stack. +const Exception assert_failed = { "Assertion Failure!" }; // If ASSERT fails. + +void except_raise(const Exception *e, const char *file,int line) { + // An exception was raised, grab the exception stack. + ExceptFrame *p = except_stack; + asserted(e != NULL); // Ensure exception pointer is not NULL + if (p == NULL) { // Uncaught Exception + const char *msg = e->reason ? e->reason : "Uncaught Exception!"; + fprintf(stderr,"\033[31m%s | Address: 0x%p | Raised at %s@%d \033[0m" ,msg,(void *)e,file,line); + fflush(stderr); + abort(); + } + // Set the exception details to the current frame. + p->exception = e; // Exception reason + p->file = file; + p->line = line; + // Move to the previous frame in the stack. + except_stack = except_stack->prev; + // Jump to the saved context environment. + longjmp(p->env, EXCEPT_RAISED); +} +void asserted(int e) { + ASSERTED(e); + (void)e; +} diff --git a/src/hash_table.c b/src/hash_table.c new file mode 100644 index 0000000..277375c --- /dev/null +++ b/src/hash_table.c @@ -0,0 +1,172 @@ +#include "../include/hash_table.h" +#include "../include/except.h" + +#include +#include +#include + +const Exception out_of_memory = { "Out Of Memory" }; // OOM Exception. + +struct Entry{ + const char *key; + void *obj; + struct Entry *next; +}; +struct Hash_Table{ + uint32_t size; + hash_func *hash; + cleanup_func *cf; + Entry **entries; +}; +static size_t hash_table_index(Hash_Table *ht, const char *key){ + size_t res = (ht->hash(key,strlen(key)) % ht->size); + return res; + +} + +Hash_Table *hash_table_new(uint32_t size, hash_func *hf, cleanup_func *cf, int flags){ + // Allocate table and populate options. + Hash_Table *ht = malloc(sizeof(*ht)); + // Error Check + if(!ht){ + if(!(flags & SOFTFAIL)) RAISE(out_of_memory); + return NULL; + } + // Populate options + ht->size = size; + ht->hash = hf; + if(cf) ht->cf = cf; + ht->cf = free; + // Zero Out Entries + ht->entries = calloc(sizeof(Entry*), ht->size); + // Error check + if(!ht->entries){ + if(!(flags & SOFTFAIL)) RAISE(out_of_memory); + return NULL; + } + // Allocation succeded return table. + return ht; +} +void hash_table_free(Hash_Table *ht){ + for(uint32_t i = 0; i < ht->size; i++){ + while(ht->entries[i]){ + Entry *tmp = ht->entries[i]; + ht->entries[i] = ht->entries[i]->next; + ht->cf(tmp->obj); + free((void*)tmp->key); + free(tmp); + } + } + free(ht->entries); + free(ht); +} +void hash_table_print(Hash_Table *ht, int printNull){ + printf("--- Hash Table ---\n"); + for (uint32_t i = 0; i < ht->size; i++){ + if(ht->entries[i] == NULL){ + // Null Entry + if(printNull) printf("\t--- %i ---\t = EMPTY\n",i); + } else { + Entry *tmp = ht->entries[i]; + while(tmp != NULL){ + printf("\t--- %i ---\t %s \t(%p)\n", i, tmp->key, tmp->obj); + tmp = tmp->next; + } + } + } + printf("--- End Table --- \n"); +} +uint64_t hash_table_create(Hash_Table *ht, const char *key, void *obj,int flags){ + // Null Check + if(key == NULL || obj == NULL || ht == NULL) return 0; + + // Only creation, no updating must delete. + size_t i = hash_table_index(ht,key); + if(hash_table_read(ht, key) != NULL) return 0; + + // Allocate Entry. + Entry *e = malloc(sizeof(*e)); + // Error check. + if(!e) { + if(!(flags & SOFTFAIL)) RAISE(out_of_memory); + return 0; + } + // Copy key for ownership. + e->key = strdup(key); + // Error check. + if(!e) { + if(!(flags & SOFTFAIL)) RAISE(out_of_memory); + return 0; + } + // Populate Entry. + e->obj = obj; + e->next = ht->entries[i]; + ht->entries[i] = e; + // Return the index of the entry. + return i; +} +void* hash_table_read(Hash_Table *ht, const char *key){ + // NULL Checks. + if(key == NULL || ht == NULL) return NULL; + // Get index of entry. + size_t i = hash_table_index(ht,key); + Entry *e = ht->entries[i]; + // Traverse list until you find the key. + while(e != NULL && strcmp(e->key,key) != 0){ + e = e->next; + } + // Didn't find anything. + if (e == NULL) return NULL; + // Found something return object to caller. + return e->obj; +} +void* hash_table_delete(Hash_Table *ht, const char *key){ + // NULL checks. + if(key == NULL || ht == NULL) return NULL; + // Get index of entry for search. + size_t i = hash_table_index(ht,key); + Entry *e = ht->entries[i]; + Entry *prev = NULL; + // Traverse list until you find the key. + while(e != NULL && strcmp(e->key,key) != 0){ + prev = e; + e = e->next; + } + // Nothing found + if( e == NULL ) return NULL; + // Found something, reconstruct list if in the middle. + if(prev == NULL){ + ht->entries[i] = e->next; + } else { + prev->next = e->next; + } + // Return the obj pointer to caller and free entry. + void *res = e->obj; + free(e); + return res; +} + +uint64_t hash_fnv0(const char *buf, size_t len){ + uint64_t result = 0; + for(size_t i = 0; i < len; i++){ + result *= FNV_PRIME; + result ^= buf[i]; + } + return result; +} +uint64_t hash_fnv1(const char *buf, size_t len){ + uint64_t result = FNV_OFFSET; + for(size_t i = 0; i < len; i++){ + result *= FNV_PRIME; + result ^= buf[i]; + } + return result; +} +uint64_t hash_fnv1a(const char *buf, size_t len){ + uint64_t result = FNV_OFFSET; + for(size_t i = 0; i < len; i++){ + result ^= buf[i]; + result *= FNV_PRIME; + } + return result; +} diff --git a/tests/01_table.c b/tests/01_table.c new file mode 100644 index 0000000..6d9063a --- /dev/null +++ b/tests/01_table.c @@ -0,0 +1,54 @@ +#include "../include/hash_table.h" +#include +#include +#include + +void generate_word(char *buf, size_t len){ + for(size_t i =0; i< len -1; i++){ + buf[i]= 'a' + (rand() %26); + + } + buf[len -1] = 0; +} +int main(int argc, char **argv){ + if(argc != 3){ + printf("Usage: \n"); + return EXIT_FAILURE; + } + + char *filename = argv[1]; + uint32_t num_guesses = atol(argv[2]); + const uint32_t tablesize = (1<<20); + + Hash_Table *ht = hash_table_new(tablesize, hash_fnv1, NULL, 0); + + FILE *fp = fopen(filename,"r"); + char buffer[4096]; + uint32_t numwords = 0; + + while(!feof(fp) && fgets(buffer, 4096, fp) != NULL){ + buffer[strcspn(buffer,"\n\r")] = 0; + char *new = malloc(strlen(buffer)+1); + strcpy(new,buffer); + hash_table_create(ht, new, new, 0); + numwords ++; + } + + fclose(fp); + printf("Loaded %d words into the table.\n", numwords); + hash_table_print(ht,0); + uint32_t good_guesses =0; + const int shortest = 3; + const int longest = 15; + + for(uint32_t i =0; i