Compare commits

...

3 Commits

Author SHA1 Message Date
c29a583ea6 Updated documentation
All checks were successful
clang-build / clang-build (push) Successful in 37s
gcc-build / gcc-build (push) Successful in 8s
2026-01-23 17:06:13 +01:00
dd6e7a9c9e Updated documentation 2026-01-12 11:58:32 +01:00
6cd90467c6 General refactoring 2026-01-07 11:08:53 +01:00
7 changed files with 1293 additions and 1243 deletions

110
README.md
View File

@@ -11,7 +11,7 @@ the standard library. It currently features:
- [**Vector**](/docs/vector.md): a growable, contiguous array of homogenous generic data types;
- [**Map**](/docs/map.md): an associative array that handles generic heterogenous data types;
- [**BigInt**](/docs/bigint.md): a data type for arbitrary large integers.
- [**BigInt**](/docs/bigint.md): a data type for arbitrary large integers.
## Usage
At its simplest, you can use this library as follows:
@@ -22,63 +22,35 @@ At its simplest, you can use this library as follows:
#include <stdio.h>
#include "src/vector.h"
/*
* Compile with: gcc main.c src/vector.c
* Output: First element: 1
* Head of vector: 16, size is now: 1
*/
// Callback functions
vector_order_t cmp_int_asc(const void *x, const void *y);
void square(void *element, void *env);
vector_order_t cmp_asc(const void *a, const void *b);
int is_even(const void *element, void *env);
void add(void *accumulator, const void *element, void *env);
/* Compile with: gcc main.c src/vector.c
* Output: '2 4'
*/
int main(void) {
// Create an integer vector of initial capacity equal to 5
vector_t *vec = vector_new(5, sizeof(int)).value.vector;
vector_t *vec = vector_new(5, sizeof(int)).value.vector; // Create a vector of integers
// Add some elements
vector_push(vec, &(int){1}); // Equivalent as below
int nums[] = {5, 2, 4, 3};
for (int idx = 0; idx < 4; idx++) { vector_push(vec, &nums[idx]); }
int nums[] = {5, 4, 1, 2, 3}; // Push some elements
for (int idx = 0; idx < 5; idx++) { vector_push(vec, &nums[idx]); }
// Sort array in ascending order: [1, 2, 3, 4, 5]
vector_sort(vec, cmp_int_asc);
vector_sort(vec, cmp_asc); // Sort vector
vector_filter(vec, is_even, NULL); // Filter even elements
// Print 1st element
const int first = *(int*)vector_get(vec, 0).value.element;
printf("First element: %d\n", first);
for (int idx = 0; idx < 2; idx++) {
printf("%d ", *(int *)vector_get(vec, idx).value.element);
}
putchar('\n');
int sum = 0;
vector_map(vec, square, NULL); // Square elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25]
vector_filter(vec, is_even, NULL); // Filter even elements: [1, 4, 9, 16, 25] -> [4, 16]
vector_reduce(vec, &sum, add, NULL); // Sum elements: [4, 16] -> 20
// Pop second element using LIFO policy
const int head = *(int*)vector_pop(vec).value.element;
printf("Head of vector: %d, size is now: %zu\n", head, vector_size(vec));
// Remove vector from memory
vector_destroy(vec);
vector_destroy(vec); // Remove vector from memory
return 0;
}
vector_order_t cmp_int_asc(const void *x, const void *y) {
int x_int = *(const int*)x;
int y_int = *(const int*)y;
vector_order_t cmp_asc(const void *a, const void *b) {
const int x = *(int *)a, y = *(int *)b;
if (x_int < y_int) return VECTOR_ORDER_LT;
if (x_int > y_int) return VECTOR_ORDER_GT;
return VECTOR_ORDER_EQ;
}
void square(void *element, void *env) {
(void)(env);
int *value = (int*)element;
*value = (*value) * (*value);
if (x < y) return VECTOR_ORDER_LT;
return (x > y) ? VECTOR_ORDER_GT : VECTOR_ORDER_EQ;
}
int is_even(const void *element, void *env) {
@@ -87,11 +59,6 @@ int is_even(const void *element, void *env) {
return (value % 2) == 0;
}
void add(void *accumulator, const void *element, void *env) {
(void)(env);
*(int*)accumulator += *(int*)element;
}
```
### `Map` usage
@@ -167,6 +134,42 @@ int main(void) {
}
```
<<<<<<< HEAD
=======
### `String` usage:
```c
#include <stdio.h>
#include "src/string.h"
/*
* Compile with: gcc main.c src/string.c
* Output: Final string: "Hello,World,😀" Splitted: ["Hello" "World" "😀" ]
*/
int main(void) {
string_t *x = string_new(" Hello, ").value.string;
string_t *x_trm = string_trim(x).value.string;
string_t *y = string_new("😀,dlroW").value.string;
string_t *y_rev = string_reverse(y).value.string;
string_t *str = string_concat(x_trm, y_rev).value.string;
string_t **strings = string_split(str, ",").value.split.strings;
printf("Final string: \"%s\" Splitted: [", str->data);
for (int idx = 0; idx < 3; idx++) { printf("\"%s\" ", strings[idx]->data); }
printf("]\n");
string_split_destroy(strings, 3); string_destroy(str);
string_destroy(x); string_destroy(y);
string_destroy(x_trm); string_destroy(y_rev);
return 0;
}
```
>>>>>>> 7d95b32 (Updated documentation)
For a more exhaustive example, refer to the `usage.c` file. There, you will find a program with proper error management
and a sample usage for every available method. To run it, first issue the following command:
@@ -184,8 +187,7 @@ This will compile the library as well as the `usage.c` file, the unit tests and
> GNU Multiple Precision Arithmetic Library (GMP).
## Documentation
For additional details about this library (internal design, memory
management, data ownership, etc.) go to the [docs folder](/docs).
For additional details about this library (internal design, memory management, data ownership, etc.) go to the [docs folder](/docs).
## Unit tests
Datum provides some unit tests for `Vector`, `Map` and `BigInt`. To run them, you can issue the following commands:

View File

@@ -46,7 +46,7 @@ The `BigInt` data structure supports the following methods:
- `bigint_result_t bigint_destroy(number)`: delete the big number;
- `bigint_result_t bigint_printf(format, ...)`: `printf` wrapper that introduces the `%B` placeholder to print big numbers. It supports variadic parameters.
As you can see by the previous function signatures, methods that operate on the
As you can see from the previous function signatures, methods that operate on the
`BigInt` data type return a custom type called `bigint_result_t` which is defined as
follows:
@@ -80,7 +80,7 @@ by setting the `status` field and by providing a descriptive message on the `mes
field. If the operation was successful (that is, `status == BIGINT_OK`), you can either
move on with the rest of the program or read the returned value from the sum data type.
Of course, you can choose to ignore the return value (if you're brave enough :D) as
illustrated in the first part of the README.
illustrated on the first part of the README.
The sum data type (i.e., the `value` union) defines four different variables. Each
of them has an unique scope as described below:

View File

@@ -5,7 +5,7 @@ aspects (internal design, memory layout, etc.) of the `Map` data structure.
`Map` is an hash table that uses open addressing with linear probing for collision
resolution and the [FNV-1a algorithm](https://en.wikipedia.org/wiki/FowlerNollVo_hash_function) as its hashing function. Resizing is performed
automatically by doubling the capacity when the load factor exceeds 75%. Internally,
this data structure is represented by the following two structures:
this data structure is represented by the following two layouts:
```c
typedef struct {
@@ -46,7 +46,7 @@ The `Map` data structure supports the following methods:
- `size_t map_size(map)`: returns map size (i.e., the number of elements);
- `size_t map_capacity(map)`: returns map capacity (i.e., map total size).
As you can see by the previous function signatures, most methods that operate
As you can see from the previous function signatures, most methods that operate
on the `Map` data type return a custom type called `map_result_t` which is
defined as follows:
@@ -73,4 +73,4 @@ Each method that returns such type indicates whether the operation was successfu
the `status` field and by providing a descriptive message on the `message` field. If the operation was
successful (that is, `status == MAP_OK`), you can either move on with the rest of the program or read
the returned value from the sum data type. Of course, you can choose to ignore the return value (if you're brave enough :D) as illustrated
in the first part of the README.
on the first part of the README.

View File

@@ -5,7 +5,7 @@ aspects (internal design, memory layout, etc.) of the `Vector` data structure.
`Vector` is a dynamic array with generic data type support; this means that you can store
any kind of homogenous value on this data structure. Resizing is performed automatically
by increasing the capacity by 1.5 times when the array becomes full. Internally, this
data structure is represented by the following structure:
data structure is represented by the following layout:
```c
typedef struct {
@@ -29,7 +29,7 @@ At the time being, `Vector` supports the following methods:
- `vector_result_t vector_push(vector, value)`: add a new value to the vector;
- `vector_result_t vector_set(vector, index, value)`: update the value of a given index if it exists;
- `vector_result_t vector_get(vector, index)`: return the value indexed by `index` if it exists;
- `map_result_t vector_sort(map, cmp)`: sort array using `cmp` function;
- `vector_result_t vector_sort(vector, cmp)`: sort vector using `cmp` function;
- `vector_result_t vector_pop(vector)`: pop last element from the vector following the LIFO policy;
- `vector_result_t vector_map(vector, callback, env)`: apply `callback` function to vector (in-place);
- `vector_result_t vector_filter(vector, callback, env)`: filter vector using `callback` (in-place);
@@ -39,7 +39,7 @@ At the time being, `Vector` supports the following methods:
- `size_t vector_size(vector)`: return vector size (i.e., the number of elements);
- `size_t vector_capacity(vector)`: return vector capacity (i.e., vector total size).
As you can see by the previous function signatures, most methods that operate
As you can see from the previous function signatures, most methods that operate
on the `Vector` data type return a custom type called `vector_result_t` which is
defined as follows:
@@ -66,7 +66,7 @@ Each method that returns such type indicates whether the operation was successfu
by setting the `status` field and by providing a descriptive message on the `message`
field. If the operation was successful (that is, `status == VECTOR_OK`), you can either
move on with the rest of the program or read the returned value from the sum data type. Of course, you can choose to
ignore the return value (if you're brave enough :D) as illustrated in the first part of the README.
ignore the return value (if you're brave enough :D) as illustrated on the first part of the README.
## Functional methods
`Vector` provides three functional methods called `map`, `filter` and `reduce` which allow the caller to apply a computation to the vector,
@@ -85,14 +85,80 @@ In particular, you should be aware of the following design choices:
- The `vector_reduce` callback method requires the caller to initialize an _"accumulator"_ variable before calling this method;
- The `vector_filter` callback method is expected to return non-zero to keep the element and zero to filter it out.
- The `env` argument is an optional parameter to pass the external environment to the callback function. It is used to mock the behavior of closures, where
the lexical environment is captured when the closure is created.
the lexical environment is captured when the closure is created;
- Callback functions must be self-contained and handle all their resources. Additionally, they are responsible for ensuring their operations
don't cause any undefined behavior.
Let's look at an example:
```c
#include <stdio.h>
#include "src/vector.h"
// Callback functions
void square(void *element, void *env);
int is_even(const void *element, void *env);
void add(void *accumulator, const void *element, void *env);
int main(void) {
// Create an integer vector of initial capacity equal to 5
vector_t *vec = vector_new(5, sizeof(int)).value.vector;
int nums[] = {1, 2, 3, 4, 5};
for (int idx = 0; idx < 5; idx++) {
vector_push(vec, &nums[idx]);
}
// Square elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25]
vector_map(vec, square, NULL);
for (int idx = 0; idx < 5; idx++) {
printf("%d ", *(int *)vector_get(vec, idx).value.element);
}
putchar('\n');
// Filter even elements: [1, 4, 9, 16, 25] -> [4, 16]
vector_filter(vec, is_even, NULL);
for (int idx = 0; idx < 2; idx++) {
printf("%d ", *(int *)vector_get(vec, idx).value.element);
}
putchar('\n');
// Sum elements: [4, 16] -> 20
int sum = 0;
vector_reduce(vec, &sum, add, NULL);
printf("%d\n", sum);
vector_destroy(vec);
return 0;
}
void square(void *element, void *env) {
(void)(env);
int *value = (int*)element;
*value = (*value) * (*value);
}
int is_even(const void *element, void *env) {
(void)(env);
int value = *(int*)element;
return (value % 2) == 0;
}
void add(void *accumulator, const void *element, void *env) {
(void)(env);
*(int*)accumulator += *(int*)element;
}
```
## Sorting
As indicated in the [its documentation](/docs/vector.md), the `Vector` data type
provides an efficient in-place sorting function called `vector_sort` that uses
a builtin implementation of the [Quicksort algorithm](https://en.wikipedia.org/wiki/Quicksort). This method requires an user-defined comparison procedure which allows the
caller to customize the sorting behavior. The comparison procedure must adhere to the
following specification:
caller to customize the sorting behavior.
The comparison procedure must adhere to the following specification:
1. Must return `vector_order_t`, which is defined as follows:
@@ -107,7 +173,7 @@ typedef enum {
and indicates the ordering relationship between any two elements.
2. Must accept two `const void*` parameters representing two elements to compare;
3. Must be self-contained and handle all its own resources.
3. Must be self-contained and handle all its resources. Additionally, it's responsible for ensuring its operations don't cause any undefined behavior.
Let's look at some examples. For instance, let's say that we want to sort an array
of integers in ascending and descending order:
@@ -117,8 +183,8 @@ of integers in ascending and descending order:
#include "src/vector.h"
vector_order_t cmp_int_asc(const void *x, const void *y) {
int x_int = *(const int*)x;
int y_int = *(const int*)y;
const int x_int = *(const int*)x;
const int y_int = *(const int*)y;
if (x_int < y_int) return VECTOR_ORDER_LT;
if (x_int > y_int) return VECTOR_ORDER_GT;

File diff suppressed because it is too large Load Diff

View File

@@ -11,10 +11,6 @@
#include "map.h"
// Internal methods
static uint64_t hash_key(const char *key);
static size_t map_insert_index(const map_t *map, const char *key);
static size_t map_find_index(const map_t *map, const char *key);
static map_result_t map_resize(map_t *map);
/**
* hash_key
@@ -22,7 +18,7 @@ static map_result_t map_resize(map_t *map);
*
* Returns the digest of @key using the Fowler-Noll-Vo hashing algorithm
*/
uint64_t hash_key(const char *key) {
static uint64_t hash_key(const char *key) {
uint64_t hash = FNV_OFFSET_BASIS_64;
while (*key) {
@@ -33,43 +29,6 @@ uint64_t hash_key(const char *key) {
return hash;
}
/**
* map_new
*
* Returns a map_result_t data type containing a new hash map
*/
map_result_t map_new(void) {
map_result_t result = {0};
map_t *map = malloc(sizeof(map_t));
if (map == NULL) {
result.status = MAP_ERR_ALLOCATE;
SET_MSG(result, "Failed to allocate memory for map");
return result;
}
map->elements = calloc(INITIAL_CAP, sizeof(map_element_t));
if (map->elements == NULL) {
free(map);
result.status = MAP_ERR_ALLOCATE;
SET_MSG(result, "Failed to allocate memory for map elements");
return result;
}
// Initialize map
map->capacity = INITIAL_CAP;
map->size = 0;
map->tombstone_count = 0;
result.status = MAP_OK;
SET_MSG(result, "Map successfully created");
result.value.map = map;
return result;
}
/**
* map_insert_index
* @map: a non-null map
@@ -80,7 +39,7 @@ map_result_t map_new(void) {
*
* Returns the index of available slot or SIZE_MAX otherwise
*/
size_t map_insert_index(const map_t *map, const char *key) {
static size_t map_insert_index(const map_t *map, const char *key) {
const uint64_t key_digest = hash_key(key);
size_t idx = key_digest % map->capacity;
size_t delete_tracker = map->capacity; // Fallback index
@@ -113,7 +72,7 @@ size_t map_insert_index(const map_t *map, const char *key) {
*
* Returns a a map_result_t data type containing the status
*/
map_result_t map_resize(map_t *map) {
static map_result_t map_resize(map_t *map) {
map_result_t result = {0};
const size_t old_capacity = map->capacity;
@@ -174,6 +133,43 @@ map_result_t map_resize(map_t *map) {
return result;
}
/**
* map_new
*
* Returns a map_result_t data type containing a new hash map
*/
map_result_t map_new(void) {
map_result_t result = {0};
map_t *map = malloc(sizeof(map_t));
if (map == NULL) {
result.status = MAP_ERR_ALLOCATE;
SET_MSG(result, "Failed to allocate memory for map");
return result;
}
map->elements = calloc(INITIAL_CAP, sizeof(map_element_t));
if (map->elements == NULL) {
free(map);
result.status = MAP_ERR_ALLOCATE;
SET_MSG(result, "Failed to allocate memory for map elements");
return result;
}
// Initialize map
map->capacity = INITIAL_CAP;
map->size = 0;
map->tombstone_count = 0;
result.status = MAP_OK;
SET_MSG(result, "Map successfully created");
result.value.map = map;
return result;
}
/**
* map_add
* @map: a non-null map

View File

@@ -10,10 +10,112 @@
#include "vector.h"
// Internal methods
static vector_result_t vector_resize(vector_t *vector);
static void swap(void *x, void *y, size_t size);
static size_t partition(void *base, size_t low, size_t high, size_t size, vector_cmp_fn cmp);
static void quicksort(void *base, size_t low, size_t high, size_t size, vector_cmp_fn cmp);
/**
* vector_resize
* @vector: a non-null vector
*
* Increases the size of @vector
*
* Returns a vector_result_t data type containing the status
*/
static vector_result_t vector_resize(vector_t *vector) {
vector_result_t result = {0};
const size_t old_capacity = vector->capacity;
const size_t new_capacity = old_capacity > 0 ? old_capacity * 2 : 1;
// Check for stack overflow errors
if (new_capacity > SIZE_MAX / vector->data_size) {
result.status = VECTOR_ERR_OVERFLOW;
SET_MSG(result, "Exceeded maximum size while resizing vector");
return result;
}
void *new_elements = realloc(vector->elements, new_capacity * vector->data_size);
if (new_elements == NULL) {
result.status = VECTOR_ERR_ALLOCATE;
SET_MSG(result, "Failed to reallocate memory for vector");
return result;
}
vector->elements = new_elements;
vector->capacity = new_capacity;
result.status = VECTOR_OK;
SET_MSG(result, "Vector successfully resized");
return result;
}
/**
* swap
* @x: first element
* @y: second element
*
* Swaps @x and @y
*/
static void swap(void *x, void *y, size_t size) {
uint8_t temp[size];
memcpy(temp, x, size);
memcpy(x, y, size);
memcpy(y, temp, size);
}
/**
* partition
* @base: the array/partition
* @low: lower index
* @high: higher index
* @size: data size
* @cmp: comparison function
*
* Divides an array into two partitions
*
* Returns the pivot index
*/
static size_t partition(void *base, size_t low, size_t high, size_t size, vector_cmp_fn cmp) {
uint8_t *arr = (uint8_t*)base;
void *pivot = arr + (high * size);
size_t i = low;
for (size_t j = low; j < high; j++) {
vector_order_t order = cmp(arr + (j * size), pivot);
if (order == VECTOR_ORDER_LT || order == VECTOR_ORDER_EQ) {
swap(arr + (i * size), arr + (j * size), size);
i++;
}
}
swap(arr + (i * size), arr + (high * size), size);
return i;
}
/**
* quicksort
* @base: the base array/partition
* @low: lower index
* @high: higher index
* @size: data size
* @cmp: comparision function
*
* Recursively sorts an array/partition using the Quicksort algorithm
*/
static void quicksort(void *base, size_t low, size_t high, size_t size, vector_cmp_fn cmp) {
if (low < high) {
const size_t pivot = partition(base, low, high, size, cmp);
if (pivot > 0) {
quicksort(base, low, pivot - 1, size, cmp);
}
quicksort(base, pivot + 1, high, size, cmp);
}
}
/**
* vector_new
@@ -61,112 +163,6 @@ vector_result_t vector_new(size_t size, size_t data_size) {
return result;
}
/**
* vector_resize
* @vector: a non-null vector
*
* Increases the size of @vector
*
* Returns a vector_result_t data type containing the status
*/
vector_result_t vector_resize(vector_t *vector) {
vector_result_t result = {0};
const size_t old_capacity = vector->capacity;
const size_t new_capacity = old_capacity > 0 ? old_capacity * 2 : 1;
// Check for stack overflow errors
if (new_capacity > SIZE_MAX / vector->data_size) {
result.status = VECTOR_ERR_OVERFLOW;
SET_MSG(result, "Exceeded maximum size while resizing vector");
return result;
}
void *new_elements = realloc(vector->elements, new_capacity * vector->data_size);
if (new_elements == NULL) {
result.status = VECTOR_ERR_ALLOCATE;
SET_MSG(result, "Failed to reallocate memory for vector");
return result;
}
vector->elements = new_elements;
vector->capacity = new_capacity;
result.status = VECTOR_OK;
SET_MSG(result, "Vector successfully resized");
return result;
}
/**
* swap
* @x: first element
* @y: second element
*
* Swaps @x and @y
*/
void swap(void *x, void *y, size_t size) {
uint8_t temp[size];
memcpy(temp, x, size);
memcpy(x, y, size);
memcpy(y, temp, size);
}
/**
* partition
* @base: the array/partition
* @low: lower index
* @high: higher index
* @size: data size
* @cmp: comparison function
*
* Divides an array into two partitions
*
* Returns the pivot index
*/
size_t partition(void *base, size_t low, size_t high, size_t size, vector_cmp_fn cmp) {
uint8_t *arr = (uint8_t*)base;
void *pivot = arr + (high * size);
size_t i = low;
for (size_t j = low; j < high; j++) {
vector_order_t order = cmp(arr + (j * size), pivot);
if (order == VECTOR_ORDER_LT || order == VECTOR_ORDER_EQ) {
swap(arr + (i * size), arr + (j * size), size);
i++;
}
}
swap(arr + (i * size), arr + (high * size), size);
return i;
}
/**
* quicksort
* @base: the base array/partition
* @low: lower index
* @high: higher index
* @size: data size
* @cmp: comparision function
*
* Recursively sorts an array/partition using the Quicksort algorithm
*/
void quicksort(void *base, size_t low, size_t high, size_t size, vector_cmp_fn cmp) {
if (low < high) {
const size_t pivot = partition(base, low, high, size, cmp);
if (pivot > 0) {
quicksort(base, low, pivot - 1, size, cmp);
}
quicksort(base, pivot + 1, high, size, cmp);
}
}
/**
* vector_push
* @vector: a non-null vector