Added functional methods (map, filter and reduce)

This commit is contained in:
2025-12-22 12:23:35 +01:00
parent 7cc2615f8b
commit feb136d393
6 changed files with 425 additions and 11 deletions

View File

@@ -24,24 +24,64 @@ At its simplest, you can use this library as follows:
/*
* Compile with: gcc main.c src/vector.c
* Output: First element: 5
* Head of vector: 6, size is now: 1
* Output: First element: 1
* Head of vector: 16, size is now: 1
*/
vector_order_t cmp_int_asc(const void *x, const void *y) {
int x_int = *(const int*)x;
int y_int = *(const int*)y;
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);
}
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;
}
int main(void) {
// Create an integer vector of initial capacity equal to 5
vector_t *vec = vector_new(5, sizeof(int)).value.vector;
// Add two numbers
int val = 5;
vector_push(vec, &val);
// Equivalent as above
vector_push(vec, &(int){6});
// 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]); }
// Sort array in ascending order: [1, 2, 3, 4, 5]
vector_sort(vec, cmp_int_asc);
// Print 1st element
const int first = *(int*)vector_get(vec, 0).value.element;
printf("First element: %d\n", first);
// Square elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25]
vector_map(vec, square, NULL);
// Filter even elements: [1, 4, 9, 16, 25] -> [4, 16]
vector_filter(vec, is_even, NULL);
// Sume elements: [4, 16] -> 20
int sum = 0;
vector_reduce(vec, &sum, add, NULL);
// 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));

View File

@@ -31,8 +31,10 @@ At the time being, `Vector` supports the following methods:
- `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_pop(vector)`: pop last element from the vector following the LIFO policy;
- `vector_result_t vector_clear(vector)`: logically reset the vector. That is, new pushes
will overwrite the memory;
- `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);
- `vector_result_t vector_reduce(vector, accumulator, callback, env)`: fold/reduce vector using `callback`;
- `vector_result_t vector_clear(vector)`: logically reset the vector. That is, new pushes will overwrite the memory;
- `vector_result_t vector_destroy(vector)`: delete the vector;
- `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).
@@ -66,5 +68,21 @@ field. If the operation was successful (that is, `status == VECTOR_OK`), you can
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.
The documentation for the `vector_sort(map, cmp)` method can be found
in [the following document](/docs/sort.md).
## Functional methods
`Vector` provides three functional methods called `map`, `filter` and `reduce` which allow the caller to apply a computation to the vector,
filter the vector according to a function and fold the vector to a single value according to a custom function, respectively.
The caller is responsible to define a custom `callback` function that satisfy the following constraints:
```c
typedef void (*map_callback_fn)(void *element, void *env);
typedef int (*vector_filter_fn)(const void *element, void *env);
typedef void (*vector_reduce_fn)(void *accumulator, const void *element, void *env);
```
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 documentation for the `vector_sort(map, cmp)` method can be found in [the following document](/docs/sort.md).

View File

@@ -383,6 +383,143 @@ vector_result_t vector_pop(vector_t *vector) {
return result;
}
/**
* vector_map
* @vector: a non-null vector
* @callback: callback function
* @env: optional captured environment
*
* Transforms each element of @vector in place by applying @callback
*
* Returns a vector_result_t data type
*/
vector_result_t vector_map(vector_t *vector, map_callback_fn callback, void *env) {
vector_result_t result = {0};
if (vector == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid vector");
return result;
}
if (callback == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid callback function");
return result;
}
for (size_t idx = 0; idx < vector->size; idx++) {
void *element = (uint8_t*)vector->elements + (idx * vector->data_size);
callback(element, env);
}
result.status = VECTOR_OK;
SET_MSG(result, "Vector successfully mapped");
return result;
}
/**
* vector_filter
* @vector: a non-null vector
* @callback: callback function
* @env: optional captured environment
*
* Filters elements from @vector using @callback.
* Elements are shifted in place, vector size is updated.
*
* Returns a vector_result_t data type
*/
vector_result_t vector_filter(vector_t *vector, vector_filter_fn callback, void *env) {
vector_result_t result = {0};
if (vector == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid vector");
return result;
}
if (callback == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid callback function");
return result;
}
size_t write_idx = 0;
for (size_t read_idx = 0; read_idx < vector->size; read_idx++) {
void *element = (uint8_t*)vector->elements + (read_idx * vector->data_size);
// Remove elements from @vector for which @callback returns zero
// If @callback returns non-zero, element is kept
if (callback(element, env)) {
if (read_idx != write_idx) {
void *dest = (uint8_t*)vector->elements + (write_idx * vector->data_size);
memcpy(dest, element, vector->data_size);
}
write_idx++;
}
}
// Update vector size
vector->size = write_idx;
result.status = VECTOR_OK;
SET_MSG(result, "Vector successfully filtered");
return result;
}
/**
* vecto_reduce
* @vector: a non-null vector
* @accumulator: pointer to accumulator value
* @callback: callback function
* @env: optional captured environment
*
* Reduces @vector to a single value by repeatedly applying @callback
* The @accumulator value should be initialized by the caller before invoking this function
*
* Returns a vector_result_t data type
*/
vector_result_t vector_reduce(const vector_t *vector, void *accumulator, vector_reduce_fn callback, void *env) {
vector_result_t result = {0};
if (vector == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid vector");
return result;
}
if (accumulator == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid accumulator");
return result;
}
if (callback == NULL) {
result.status = VECTOR_ERR_INVALID;
SET_MSG(result, "Invalid callback function");
return result;
}
for (size_t idx = 0; idx < vector->size; idx++) {
const void *element = (uint8_t*)vector->elements + (idx * vector->data_size);
callback(accumulator, element, env);
}
result.status = VECTOR_OK;
SET_MSG(result, "Vector successfully reduced");
return result;
}
/**
* vector_clear
* @vector: a non-null vector

View File

@@ -36,7 +36,11 @@ typedef enum {
VECTOR_ORDER_GT
} vector_order_t;
// Callback functions
typedef vector_order_t (*vector_cmp_fn)(const void *x, const void *y);
typedef void (*map_callback_fn)(void *element, void *env);
typedef int (*vector_filter_fn)(const void *element, void *env);
typedef void (*vector_reduce_fn)(void *accumulator, const void *element, void *env);
#ifdef __cplusplus
extern "C" {
@@ -49,6 +53,9 @@ vector_result_t vector_set(vector_t *vector, size_t index, void *value);
vector_result_t vector_get(vector_t *vector, size_t index);
vector_result_t vector_sort(vector_t *vector, vector_cmp_fn cmp);
vector_result_t vector_pop(vector_t *vector);
vector_result_t vector_map(vector_t *vector, map_callback_fn callback, void *env);
vector_result_t vector_filter(vector_t *vector, vector_filter_fn callback, void *env);
vector_result_t vector_reduce(const vector_t *vector, void *accumulator, vector_reduce_fn callback, void *env);
vector_result_t vector_clear(vector_t *vector);
vector_result_t vector_destroy(vector_t *vector);

View File

@@ -312,6 +312,114 @@ void test_vector_sort_struct_by_name(void) {
vector_destroy(people);
}
// Map vector elements
void square(void *element, void *env) {
(void)(env);
int *value = (int*)element;
*value = (*value) * (*value);
}
void test_vector_map(void) {
vector_result_t res = vector_new(5, sizeof(int));
assert(res.status == VECTOR_OK);
vector_t *v = res.value.vector;
int values[] = { 25, 4, 3, 12, 19, 45 };
for (size_t idx = 0; idx < 6; idx++) {
vector_push(v, &values[idx]);
}
vector_result_t square_res = vector_map(v, square, NULL);
assert(square_res.status == VECTOR_OK);
const int expected[] = { 625, 16, 9, 144, 361, 2025 };
const size_t sz = vector_size(v);
for (size_t idx = 0; idx < sz; idx++) {
int *val = (int*)vector_get(v, idx).value.element;
assert(*val == expected[idx]);
}
vector_destroy(v);
}
// Filter vector elements
typedef struct {
double temperature;
uint64_t timestamp;
} weather_record_t;
typedef struct {
double min_temp;
double max_temp;
} temp_threshold_t;
int is_temp_in_range(const void *element, void *env) {
const weather_record_t *weather = (const weather_record_t*)element;
temp_threshold_t *threshold = (temp_threshold_t*)env;
return weather->temperature >= threshold->min_temp &&
weather->temperature <= threshold->max_temp;
}
void test_vector_filter(void) {
vector_result_t res = vector_new(5, sizeof(weather_record_t));
assert(res.status == VECTOR_OK);
vector_t *v = res.value.vector;
for (size_t idx = 0; idx < 10; idx++) {
weather_record_t record = {
.temperature = 20.0 + (idx * 2.5), // between 20.0C and 42.5C
.timestamp = 1234567890 + idx
};
vector_push(v, &record);
}
// Filter elements outside the threshold
temp_threshold_t threshold = {
.min_temp = 15.0,
.max_temp = 40.0
};
vector_result_t filter_res = vector_filter(v, is_temp_in_range, &threshold);
assert(filter_res.status == VECTOR_OK);
for (size_t idx = 0; idx < vector_size(v); idx++) {
double *val = (double*)vector_get(v, idx).value.element;
assert((*val >= 20.0) && (*val <= 40));
}
vector_destroy(v);
}
// Test reduce
void add(void *accumulator, const void *element, void *env) {
(void)(env);
*(int*)accumulator += *(int*)element;
}
void test_vector_reduce(void) {
vector_result_t res = vector_new(5, sizeof(int));
assert(res.status == VECTOR_OK);
vector_t *v = res.value.vector;
int values[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (size_t idx = 0; idx < 10; idx++) {
vector_push(v, &values[idx]);
}
int sum = 0;
vector_result_t reduce_res = vector_reduce(v, &sum, add, NULL);
assert(reduce_res.status == VECTOR_OK);
assert(sum == ((10 * 11) / 2));
vector_destroy(v);
}
// Set vector element
void test_vector_set(void) {
vector_result_t res = vector_new(5, sizeof(int));
@@ -491,6 +599,9 @@ int main(void) {
TEST(vector_sort_string);
TEST(vector_sort_struct_by_age);
TEST(vector_sort_struct_by_name);
TEST(vector_map);
TEST(vector_filter);
TEST(vector_reduce);
TEST(vector_set);
TEST(vector_set_ofb);
TEST(vector_pop);

101
usage.c
View File

@@ -16,6 +16,8 @@
puts("\n"); \
} while(0)
#define UNUSED(X) (void)(X)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -30,6 +32,9 @@ static int bigint_usage(void);
static vector_order_t cmp_int_asc(const void *x, const void *y);
static vector_order_t cmp_int_desc(const void *x, const void *y);
static void square(void *element, void *env);
static int is_even(const void *element, void *env);
static void adder(void *accumulator, const void *element, void *env);
int main(void) {
int st;
@@ -64,6 +69,24 @@ vector_order_t cmp_int_desc(const void *x, const void *y) {
return cmp_int_asc(y, x);
}
void square(void *element, void *env) {
UNUSED(env);
int *value = (int*)element;
*value = (*value) * (*value);
}
int is_even(const void *element, void *env) {
UNUSED(env);
int value = *(int*)element;
return (value % 2) == 0;
}
void adder(void *accumulator, const void *element, void *env) {
UNUSED(env);
*(int*)accumulator += *(int*)element;
}
int vector_usage(void) {
// Create a vector of 3 integers
vector_result_t res = vector_new(3, sizeof(int));
@@ -197,6 +220,84 @@ int vector_usage(void) {
}
printf("\n\n");
vector_result_t map_clear_res = vector_clear(vector);
if (map_clear_res.status != VECTOR_OK) {
printf("Cannot clear vector: %s\n", map_clear_res.message);
return 1;
}
// Map vector elements
for (size_t idx = 1; idx <= 5; idx++) {
vector_result_t map_push_res = vector_push(vector, &idx);
if (map_push_res.status != VECTOR_OK) {
printf("Error while adding elements: %s\n", map_push_res.message);
return 1;
}
}
sz = vector_size(vector);
// Square vector elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25]
vector_result_t map_res = vector_map(vector, square, NULL);
if (map_res.status != VECTOR_OK) {
printf("Error while mapping vector: %s\n", map_res.message);
return 1;
}
printf("Squared vector: ");
for (size_t idx = 0; idx < sz; idx++) {
vector_result_t map_get_res = vector_get(vector, idx);
if (map_get_res.status != VECTOR_OK) {
printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message);
return 1;
} else {
int *val = (int*)map_get_res.value.element;
printf("%d ", *val);
}
}
printf("\n");
// Filter vector elements: [1, 4, 9, 16, 25] -> [4, 16]
vector_result_t filter_res = vector_filter(vector, is_even, NULL);
if (filter_res.status != VECTOR_OK) {
printf("Error while filtering vector: %s\n", filter_res.message);
return 1;
}
sz = vector_size(vector);
printf("Filtered vector: ");
for (size_t idx = 0; idx < sz; idx++) {
vector_result_t map_get_res = vector_get(vector, idx);
if (map_get_res.status != VECTOR_OK) {
printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message);
return 1;
} else {
int *val = (int*)map_get_res.value.element;
printf("%d ", *val);
}
}
printf("\n");
// Reduce vector elements: [4, 16] -> 20
int sum = 0;
vector_result_t reduce_res = vector_reduce(vector, &sum, adder, NULL);
if (reduce_res.status != VECTOR_OK) {
printf("Error while reducing vector: %s\n", reduce_res.message);
return 1;
}
printf("Sum of vector: %d\n\n", sum);
// Free vector
vector_result_t del_res = vector_destroy(vector);
if (del_res.status != VECTOR_OK) {