Add primitives, hash map and dynamic array

This commit is contained in:
Hugo Mårdbrink 2025-04-17 09:53:37 +02:00
commit 2f19f82116
10 changed files with 384 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
build/
.DS_Store
.cache/
compile_commands.json

31
CMakeLists.txt Normal file
View file

@ -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()

52
README.md Normal file
View file

@ -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
```

View file

@ -0,0 +1,22 @@
#ifndef DYNAMIC_ARRAY_H
#define DYNAMIC_ARRAY_H
#include <htd/primitives/primitives.h>
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

View file

@ -0,0 +1,28 @@
#ifndef HTD_HASH_MAP_H
#define HTD_HASH_MAP_H
#include <htd/primitives/primitives.h>
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

View file

@ -0,0 +1,24 @@
#ifndef PRIMITIVES_H
#define PRIMITIVES_H
#include <stddef.h>
#include <stdint.h>
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

View file

@ -0,0 +1,36 @@
#include <htd/primitives/primitives.h>
#include <htd/data_structure/dynamic_array.h>
#include <stdlib.h>
#include <memory.h>
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;
}

View file

@ -0,0 +1,142 @@
#include <htd/primitives/primitives.h>
#include <htd/data_structure/hash_map.h>
#include <stdlib.h>
#include <memory.h>
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;
}

View file

@ -0,0 +1,20 @@
#include <htd/primitives/primitives.h>
#include <htd/data_structure/dynamic_array.h>
#include <assert.h>
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);
}

25
tests/test_hash_map.c Normal file
View file

@ -0,0 +1,25 @@
#include <htd/primitives/primitives.h>
#include <htd/data_structure/hash_map.h>
#include <assert.h>
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;
}