commit 2f19f82116ad7d46bb4702cc21da1097b8c357b5 Author: Hugo MÄrdbrink Date: Thu Apr 17 09:53:37 2025 +0200 Add primitives, hash map and dynamic array diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..883216e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +.DS_Store +.cache/ +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9b9a5f2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.21) + +project(htd C) +set(CMAKE_C_STANDARD 23) +set(CMAKE_C_STANDARD_REQUIRED YES) +set(CMAKE_C_EXTENSIONS NO) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +file(GLOB HTD_SOURCES + src/data_structure/*.c +) + +add_library(htd STATIC ${HTD_SOURCES}) + +target_include_directories(htd PUBLIC + ${PROJECT_SOURCE_DIR}/include +) +target_compile_options(htd PRIVATE -Wall -Wextra -Werror) + +set(CMAKE_CTEST_VERBOSE TRUE) +enable_testing() + +file(GLOB TEST_SOURCES tests/test_*.c) + +foreach(test_src ${TEST_SOURCES}) + get_filename_component(test_name ${test_src} NAME_WE) + add_executable(${test_name} ${test_src}) + target_link_libraries(${test_name} PRIVATE htd) + add_test(NAME ${test_name} COMMAND ${test_name}) +endforeach() diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe74649 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Personal library for C + +## Features +### Primitives +Use shorter more concise names for common C types. +- `i8`: 8-bit signed integer +- `u8`: 8-bit unsigned integer +- `i16`: 16-bit signed integer +- `u16`: 16-bit unsigned integer +- `i32`: 32-bit signed integer +- `u32`: 32-bit unsigned integer +- `i64`: 64-bit signed integer +- `u64`: 64-bit unsigned integer +- `f32`: 32-bit floating point +- `f64`: 64-bit floating point +- `usize`: Unsigned integer of the same size as a pointer +- `isize`: Signed integer of the same size as a pointer + +### Data structures +- `Dynamic array`: A dynamic array that can grow and shrink in size. +- `Hash map`: A hash map that uses linear probing for collision resolution and Murmur3 for hashing. + +## Building + +```bash +mkdir build +cd build +cmake .. +make +``` + +## Testing + +```bash +cd build +ctest +``` + +## Installing + +```bash +cd build +make install +``` + +## Uninstalling + +```bash +cd build +make uninstall +``` + diff --git a/include/htd/data_structure/dynamic_array.h b/include/htd/data_structure/dynamic_array.h new file mode 100644 index 0000000..2196f86 --- /dev/null +++ b/include/htd/data_structure/dynamic_array.h @@ -0,0 +1,22 @@ +#ifndef DYNAMIC_ARRAY_H +#define DYNAMIC_ARRAY_H + +#include + +typedef struct { + void *data; + + usize len; + usize data_size; + usize capacity; +} DynamicArray; + +void dynarr_init(DynamicArray* arr, usize size); + +void* dynarr_at(DynamicArray* arr, usize idx); + +void dynarr_push(DynamicArray* arr, void* data); + +void dynarr_free(DynamicArray* arr); + +#endif // DYNAMIC_ARRAY_H diff --git a/include/htd/data_structure/hash_map.h b/include/htd/data_structure/hash_map.h new file mode 100644 index 0000000..d9c6522 --- /dev/null +++ b/include/htd/data_structure/hash_map.h @@ -0,0 +1,28 @@ +#ifndef HTD_HASH_MAP_H +#define HTD_HASH_MAP_H + +#include + +typedef struct { + void* key; + void* val; +} HashMapEntry; + +typedef struct { + HashMapEntry* table; + + usize len; + usize capacity; + usize key_size; + usize val_size; +} HashMap; + +void hmap_init(HashMap* hmap, usize key_size, usize val_size); + +void hmap_put(HashMap* hmap, const void* key, const void* val); + +void* hmap_get(HashMap* hmap, const void* key); + +void hmap_free(HashMap* hmap); + +#endif // HTD_HASH_MAP_H diff --git a/include/htd/primitives/primitives.h b/include/htd/primitives/primitives.h new file mode 100644 index 0000000..efd36f9 --- /dev/null +++ b/include/htd/primitives/primitives.h @@ -0,0 +1,24 @@ +#ifndef PRIMITIVES_H +#define PRIMITIVES_H + +#include +#include + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +// Floats not guaranteed per IEEE 754, but for use case it's fine +typedef float f32; +typedef double f64; + +typedef size_t usize; +typedef ptrdiff_t isize; + +#endif // PRIMITIVES_H diff --git a/src/data_structure/dynamic_array.c b/src/data_structure/dynamic_array.c new file mode 100644 index 0000000..ef9c5d4 --- /dev/null +++ b/src/data_structure/dynamic_array.c @@ -0,0 +1,36 @@ +#include +#include + +#include +#include + +const usize GROWTH_FACTOR = 2; +const usize START_LEN = 32; + +void dynarr_init(DynamicArray* arr, usize data_size) { + arr->data_size = data_size; + arr->len = 0; + arr->capacity = START_LEN; + arr->data = (void*)malloc(arr->capacity * arr->data_size); +} + +void* dynarr_at(DynamicArray* arr, usize idx) { + return &((u8*)arr->data)[arr->data_size * idx]; +} + +void dynarr_push(DynamicArray* arr, void* data) { + if (arr->len >= arr->capacity) { + arr->capacity *= GROWTH_FACTOR; + arr->data = (void*)realloc(arr->data, arr->capacity * arr->data_size); + } + + memcpy(&((u8*)arr->data)[arr->data_size * arr->len], data, arr->data_size); + arr->len++; +} + +void dynarr_free(DynamicArray* arr) { + free(arr->data); + arr->data = NULL; + arr->capacity = 0; + arr->len = 0; +} diff --git a/src/data_structure/hash_map.c b/src/data_structure/hash_map.c new file mode 100644 index 0000000..946ac7c --- /dev/null +++ b/src/data_structure/hash_map.c @@ -0,0 +1,142 @@ +#include +#include + +#include +#include + +const usize START_LEN = 16; +const usize GROWTH_FACTOR = 2; + +usize hash(const u8* key, usize len) { + const u8 *data = (const u8*)key; + u32 h = 0x811c9dc5; + const u32 c1 = 0xcc9e2d51; + const u32 c2 = 0x1b873593; + + const usize nblocks = len / 4; + for (usize i = 0; i < nblocks; i++) { + u32 k = *((u32*)data); + data += 4; + + k *= c1; + k = (k << 15) | (k >> 17); + k *= c2; + + h ^= k; + h = (h << 13) | (h >> 19); + h = h * 5 + 0xe6546b64; + } + + const usize tail_size = len & 3; + u32 k1 = 0; + if (tail_size > 0) { + for (usize i = 0; i < tail_size; ++i) { + k1 ^= data[i] << (i * 8); + } + + k1 *= c1; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= c2; + h ^= k1; + } + + h ^= len; + h = h ^ (h >> 16); + h *= 0x85ebca6b; + h = h ^ (h >> 13); + h *= 0xc2b2ae35; + h = h ^ (h >> 16); + + return h; +} + +void hmap_init(HashMap* hmap, usize key_size, usize val_size) { + hmap->len = 0; + hmap->capacity = START_LEN; + hmap->key_size = key_size; + hmap->val_size = val_size; + + hmap->table = (HashMapEntry*)malloc(sizeof(HashMapEntry) * hmap->capacity); + for (usize i = 0; i < hmap->capacity; ++i) { + hmap->table[i].key = NULL; + hmap->table[i].val = NULL; + } +} + +void resize(HashMap* hmap) { + usize old_capacity = hmap->capacity; + HashMapEntry* old_table = (HashMapEntry*)malloc(sizeof(HashMapEntry) * old_capacity); + memcpy(old_table, hmap->table, sizeof(HashMapEntry) * old_capacity); + + hmap->capacity *= GROWTH_FACTOR; + hmap->table = (HashMapEntry*)realloc(hmap->table, sizeof(HashMapEntry) * hmap->capacity); + + for (usize i = 0; i < hmap->capacity; i++) { + hmap->table[i].key = NULL; + hmap->table[i].val = NULL; + } + + // Reset length, re-adding + hmap->len = 0; + for (usize i = 0; i < old_capacity; i++) { + if (old_table[i].key != NULL) { + hmap_put(hmap, old_table[i].key, old_table[i].val); + + free(old_table[i].key); + free(old_table[i].val); + } + } + + free(old_table); +} + +void hmap_put(HashMap* hmap, const void* key, const void* val) { + if (hmap->len >= hmap->capacity) { + resize(hmap); + } + + usize idx = hash(key, hmap->key_size) % hmap->capacity; + + while (hmap->table[idx].key != NULL) { + if (memcmp(hmap->table[idx].key, key, hmap->key_size) == 0) { + free(hmap->table[idx].val); + hmap->table[idx].val = malloc(hmap->val_size); + memcpy(hmap->table[idx].val, val, hmap->val_size); + return; + } + idx = (idx + 1) % hmap->capacity; + } + + hmap->table[idx].key = malloc(hmap->key_size); + memcpy(hmap->table[idx].key, key, hmap->key_size); + hmap->table[idx].val = malloc(hmap->val_size); + memcpy(hmap->table[idx].val, val, hmap->val_size); + + hmap->len++; +} + +void* hmap_get(HashMap* hmap, const void* key) { + usize idx = hash(key, hmap->key_size) % hmap->capacity; + + while (hmap->table[idx].key != NULL) { + if (memcmp(hmap->table[idx].key, key, hmap->key_size) == 0) { + return hmap->table[idx].val; + } + idx = (idx + 1) % hmap->capacity; + } + + return NULL; +} + +void hmap_free(HashMap* hmap) { + for (usize i = 0; i < hmap->capacity; ++i) { + if (hmap->table[i].key != NULL) { + free(hmap->table[i].key); + free(hmap->table[i].val); + } + } + free(hmap->table); + hmap->table = NULL; + hmap->capacity = 0; + hmap->len = 0; +} diff --git a/tests/test_dynamic_array.c b/tests/test_dynamic_array.c new file mode 100644 index 0000000..0de6cc3 --- /dev/null +++ b/tests/test_dynamic_array.c @@ -0,0 +1,20 @@ +#include +#include + +#include + +int main() { + DynamicArray i32_array; + dynarr_init(&i32_array, sizeof(i32)); + + for (i32 i = 0; i < 100; i++) { + dynarr_push(&i32_array, &i); + } + + assert(i32_array.len == 100); + for (i32 i = 0; i < 100; i++) { + assert(*(i32*)dynarr_at(&i32_array, i) == i); + } + + dynarr_free(&i32_array); +} diff --git a/tests/test_hash_map.c b/tests/test_hash_map.c new file mode 100644 index 0000000..2fb42a2 --- /dev/null +++ b/tests/test_hash_map.c @@ -0,0 +1,25 @@ +#include +#include + +#include + +int main() { + HashMap i32_i64_hmap; + hmap_init(&i32_i64_hmap, sizeof(i32), sizeof(i64)); + + for (i32 i = 0; i < 100; i++) { + i64 v = (i64)i * 2; + hmap_put(&i32_i64_hmap, &i, &v); + } + + for (i32 i = 0; i < 100; i++) { + i64 expected_value = (i64)i * 2; + i64* retrieved_value = (i64*)hmap_get(&i32_i64_hmap, &i); + + assert(retrieved_value != NULL && *retrieved_value == expected_value); + } + + hmap_free(&i32_i64_hmap); + + return 0; +}