From feb136d3936e643d7e0b35f4c87c44640db43b27 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Mon, 22 Dec 2025 12:23:35 +0100 Subject: [PATCH] Added functional methods (`map`, `filter` and `reduce`) --- README.md | 54 ++++++++++++++--- docs/vector.md | 26 +++++++-- src/vector.c | 137 ++++++++++++++++++++++++++++++++++++++++++++ src/vector.h | 7 +++ tests/test_vector.c | 111 +++++++++++++++++++++++++++++++++++ usage.c | 101 ++++++++++++++++++++++++++++++++ 6 files changed, 425 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 417088d..0d4c9a8 100644 --- a/README.md +++ b/README.md @@ -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)); diff --git a/docs/vector.md b/docs/vector.md index a271eef..4f10d4f 100644 --- a/docs/vector.md +++ b/docs/vector.md @@ -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). \ No newline at end of file +## 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). diff --git a/src/vector.c b/src/vector.c index 089b750..c999ee6 100644 --- a/src/vector.c +++ b/src/vector.c @@ -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 diff --git a/src/vector.h b/src/vector.h index 6228783..e882a2a 100644 --- a/src/vector.h +++ b/src/vector.h @@ -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); diff --git a/tests/test_vector.c b/tests/test_vector.c index 52fdcac..93bec1d 100644 --- a/tests/test_vector.c +++ b/tests/test_vector.c @@ -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); diff --git a/usage.c b/usage.c index e771fb5..c643083 100644 --- a/usage.c +++ b/usage.c @@ -16,6 +16,8 @@ puts("\n"); \ } while(0) +#define UNUSED(X) (void)(X) + #include #include #include @@ -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) {