diff --git a/README.md b/README.md index bc913b1..5193b4f 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ The `Vector` data structure 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_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; @@ -241,6 +242,178 @@ Just like for the `Map` data structure, 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. +## Sorting +The `Vector` data structure provides an efficient in-place sorting method called `vector_sort` +which uses a builtin [Quicksort](https://en.wikipedia.org/wiki/Quicksort) implementation. This +function requires an user-defined comparison procedure as its second parameter, which allows +the caller to customize the sorting behavior. It must adhere to the following specification: + +1. Must return `vector_order_t`, which is defined as follows: + +```c +typedef enum { + VECTOR_ORDER_LT = 0x0, // First element should come before the second + VECTOR_ORDER_EQ, // The two elements are equivalent + VECTOR_ORDER_GT // First element should come after the second +} vector_order_t; +``` + +and indicates the ordering relationship between any two elements. + +2. Must accept two `const void*` parameters representing the two elements to compare; +3. Must be self-contained and handle all its own resources. + +Let's look at some examples; for instance, let's sort an integer array in ascending and +descending order: + +```c +#include +#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; + + if (x_int < y_int) return VECTOR_ORDER_LT; + if (x_int > y_int) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +vector_order_t cmp_int_desc(const void *x, const void *y) { + return cmp_int_asc(y, x); +} + +/* + * Compile with: gcc main.c src/vector.h + * Output: Before sorting: -8 20 -10 125 34 9 + * After sorting (ascending order): -10 -8 9 20 34 125 + * After sorting (descending order): 125 34 20 9 -8 -10 + */ +int main(void) { + vector_t *v = vector_new(5, sizeof(int)).value.vector; + + int values[] = { -8, 20, -10, 125, 34, 9 }; + for (size_t idx = 0; idx < 6; idx++) { + vector_push(v, &values[idx]); + } + + // Print unsorted array + printf("Before sorting: "); + for (size_t idx = 0; idx < vector_size(v); idx++) { + printf("%d ", *(int*)vector_get(v, idx).value.element); + } + + // Sort array in ascending order + vector_sort(v, cmp_int_asc); + + // Print sorted array + printf("\nAfter sorting (ascending order): "); + for (size_t idx = 0; idx < vector_size(v); idx++) { + printf("%d ", *(int*)vector_get(v, idx).value.element); + } + + // Sort array in descending order + vector_sort(v, cmp_int_desc); + + // Print sorted array + printf("\nAfter sorting (descending order): "); + for (size_t idx = 0; idx < vector_size(v); idx++) { + printf("%d ", *(int*)vector_get(v, idx).value.element); + } + + printf("\n"); + + vector_destroy(v); + + return 0; +} +``` + +Obviously, you can use the `vector_sort` method on custom data types as well. For instance, let's suppose that you have a +struct representing employees and you want to sort them based on their age and based on their name (lexicographic sort): + +```c +#include +#include +#include "src/vector.h" + +typedef struct { + char name[256]; + int age; +} Employee; + +vector_order_t cmp_person_by_age(const void *x, const void *y) { + const Employee *x_person = (const Employee*)x; + const Employee *y_person = (const Employee*)y; + + if (x_person->age < y_person->age) return VECTOR_ORDER_LT; + if (x_person->age > y_person->age) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +vector_order_t cmp_person_by_name(const void *x, const void *y) { + const Employee *x_person = (const Employee*)x; + const Employee *y_person = (const Employee*)y; + + const int result = strcmp(x_person->name, y_person->name); + + if(result < 0) return VECTOR_ORDER_LT; + if(result > 0) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +/* + * Compile with: gcc main.c src/vector.h + * Output: Sort by age: + * Name: Marco, Age: 25 + * Name: Alice, Age: 28 + * Name: Bob, Age: 45 + * + * Sort by name: + * Name: Alice, Age: 28 + * Name: Bob, Age: 45 + * Name: Marco, Age: 25 + */ +int main(void) { + vector_t *employees = vector_new(5, sizeof(Employee)).value.vector; + + Employee e1 = { .name = "Bob", .age = 45 }; + Employee e2 = { .name = "Alice", .age = 28 }; + Employee e3 = { .name = "Marco", .age = 25 }; + + vector_push(employees, &e1); + vector_push(employees, &e2); + vector_push(employees, &e3); + + // Sort array by age + vector_sort(employees, cmp_person_by_age); + + // Print sorted array + printf("Sort by age:\n"); + for (size_t idx = 0; idx < vector_size(employees); idx++) { + Employee *p = (Employee*)vector_get(employees, idx).value.element; + printf("Name: %s, Age: %d\n", p->name, p->age); + } + + // Sort array by name + vector_sort(employees, cmp_person_by_name); + + // Print sorted array + printf("\nSort by name:\n"); + for (size_t idx = 0; idx < vector_size(employees); idx++) { + Employee *p = (Employee*)vector_get(employees, idx).value.element; + printf("Name: %s, Age: %d\n", p->name, p->age); + } + + vector_destroy(employees); + + return 0; +} +``` + ## Unit tests Datum provides some unit tests for both the `Vector` and the `Map` data types. To run them, you can issue the following commands: diff --git a/src/vector.c b/src/vector.c index aae5b36..9053494 100644 --- a/src/vector.c +++ b/src/vector.c @@ -7,8 +7,11 @@ #include "vector.h" -// Internal method to increase vector size +// 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_new @@ -37,7 +40,7 @@ vector_result_t vector_new(size_t size, size_t data_size) { } // Initialize vector - vector->count = 0; + vector->size = 0; vector->capacity = size; vector->data_size = data_size; vector->elements = calloc(size, data_size); @@ -94,6 +97,73 @@ vector_result_t vector_resize(vector_t *vector) { 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 @@ -114,7 +184,7 @@ vector_result_t vector_push(vector_t *vector, void *value) { } // Check whether vector has enough space available - if (vector->capacity == vector->count) { + if (vector->capacity == vector->size) { result = vector_resize(vector); if (result.status != VECTOR_OK) { return result; @@ -122,7 +192,7 @@ vector_result_t vector_push(vector_t *vector, void *value) { } // Calculate destination memory address - uint8_t *destination_addr = (uint8_t*)vector->elements + (vector->count * vector->data_size); + uint8_t *destination_addr = (uint8_t*)vector->elements + (vector->size * vector->data_size); // Append @value to the data structure according to its data type if (vector->data_size == sizeof(int)) { @@ -138,7 +208,7 @@ vector_result_t vector_push(vector_t *vector, void *value) { } // Increase elements count - vector->count++; + vector->size++; result.status = VECTOR_OK; SET_MSG(result, "Value successfully added"); @@ -166,7 +236,7 @@ vector_result_t vector_set(vector_t *vector, size_t index, void *value) { return result; } - if (index >= vector->count) { + if (index >= vector->size) { result.status = VECTOR_ERR_OVERFLOW; SET_MSG(result, "Index out of bounds"); @@ -211,7 +281,7 @@ vector_result_t vector_get(vector_t *vector, size_t index) { return result; } - if (index >= vector->count) { + if (index >= vector->size) { result.status = VECTOR_ERR_OVERFLOW; SET_MSG(result, "Index out of bounds"); @@ -225,6 +295,48 @@ vector_result_t vector_get(vector_t *vector, size_t index) { return result; } +/** + * vector_sort + * @vector: a non-null vector + * @cmp: a user-defined comparison function returning vector_order_t + * + * Sorts @vector using Quicksort algorithm and the @cmp comparison function + * + * Returns a vecto_result_t data type + */ +vector_result_t vector_sort(vector_t *vector, vector_cmp_fn cmp) { + vector_result_t result = {0}; + + if (vector == NULL) { + result.status = VECTOR_ERR_INVALID; + SET_MSG(result, "Invalid vector"); + + return result; + } + + if (cmp == NULL) { + result.status = VECTOR_ERR_INVALID; + SET_MSG(result, "Invalid comparison function"); + + return result; + } + + // The vector is already sorted + if (vector->size <= 1) { + result.status = VECTOR_OK; + SET_MSG(result, "Vector successfully sorted"); + + return result; + } + + quicksort(vector->elements, 0, vector->size - 1, vector->data_size, cmp); + + result.status = VECTOR_OK; + SET_MSG(result, "Vector successfully sorted"); + + return result; +} + /** * vector_pop * @vector: a non-null vector @@ -244,7 +356,7 @@ vector_result_t vector_pop(vector_t *vector) { return result; } - if (vector->count == 0) { + if (vector->size == 0) { result.status = VECTOR_ERR_UNDERFLOW; SET_MSG(result, "Vector is empty"); @@ -252,14 +364,14 @@ vector_result_t vector_pop(vector_t *vector) { } // Pop an element from the vector - const size_t index = (vector->count - 1); + const size_t index = (vector->size - 1); vector_result_t popped_res = vector_get(vector, index); if (popped_res.status != VECTOR_OK) { return popped_res; } - vector->count--; + vector->size--; result.status = VECTOR_OK; SET_MSG(result, "Value successfully popped"); @@ -286,7 +398,7 @@ vector_result_t vector_clear(vector_t *vector) { return result; } - vector->count = 0; + vector->size = 0; result.status = VECTOR_OK; SET_MSG(result, "Vector successfully cleared"); diff --git a/src/vector.h b/src/vector.h index 4642c8f..6228783 100644 --- a/src/vector.h +++ b/src/vector.h @@ -15,7 +15,7 @@ typedef enum { } vector_status_t; typedef struct { - size_t count; + size_t size; size_t capacity; size_t data_size; void *elements; @@ -30,6 +30,14 @@ typedef struct { } value; } vector_result_t; +typedef enum { + VECTOR_ORDER_LT = 0x0, + VECTOR_ORDER_EQ, + VECTOR_ORDER_GT +} vector_order_t; + +typedef vector_order_t (*vector_cmp_fn)(const void *x, const void *y); + #ifdef __cplusplus extern "C" { #endif @@ -39,13 +47,14 @@ vector_result_t vector_new(size_t size, size_t data_size); vector_result_t vector_push(vector_t *vector, void *value); 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_clear(vector_t *vector); vector_result_t vector_destroy(vector_t *vector); // Inline methods static inline size_t vector_size(const vector_t *vector) { - return vector ? vector->count : 0; + return vector ? vector->size : 0; } static inline size_t vector_capacity(const vector_t *vector) { diff --git a/tests/test_vector.c b/tests/test_vector.c index 0017bfa..0101851 100644 --- a/tests/test_vector.c +++ b/tests/test_vector.c @@ -104,6 +104,206 @@ void test_vector_get_ofb() { vector_destroy(v); } +// Sort integers in ascending order +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; +} + +vector_order_t cmp_int_desc(const void *x, const void *y) { + return cmp_int_asc(y, x); +} + +void test_vector_sort_int_asc() { + vector_result_t res = vector_new(5, sizeof(int)); + + assert(res.status == VECTOR_OK); + vector_t *v = res.value.vector; + + int values[] = { 25, 4, 12, -7, 25, 71, 1, 6 }; + for (size_t idx = 0; idx < 8; idx++) { + vector_push(v, &values[idx]); + } + + vector_result_t sort_res = vector_sort(v, cmp_int_asc); + assert(sort_res.status == VECTOR_OK); + + const int expected[] = { -7, 1, 4, 6, 12, 25, 25, 71 }; + for (size_t idx = 0; idx < vector_size(v); idx++) { + int *val = (int*)vector_get(v, idx).value.element; + assert(*val == expected[idx]); + } + + vector_destroy(v); +} + +void test_vector_sort_int_desc() { + vector_result_t res = vector_new(5, sizeof(int)); + + assert(res.status == VECTOR_OK); + vector_t *v = res.value.vector; + + int values[] = { 25, 4, 12, -7, 25, 71, 1, 6 }; + for (size_t idx = 0; idx < 8; idx++) { + vector_push(v, &values[idx]); + } + + vector_result_t sort_res = vector_sort(v, cmp_int_desc); + assert(sort_res.status == VECTOR_OK); + + const int expected[] = { 71, 25, 25, 12, 6, 4, 1, -7 }; + for (size_t idx = 0; idx < vector_size(v); idx++) { + int *val = (int*)vector_get(v, idx).value.element; + assert(*val == expected[idx]); + } + + vector_destroy(v); +} + +// Sort strings in descending order +vector_order_t cmp_string_desc(const void *x, const void *y) { + const char *x_str = *(const char* const*)x; + const char *y_str = *(const char* const*)y; + + // strcmp() returns an integer indicating the result of the comparison, as follows: + // - 0, if the s1 and s2 are equal; + // - a negative value if s1 is less than s2; + // - a positive value if s1 is greater than s2. + // for descending order, just invert the result + const int result = strcmp(x_str, y_str); + + if (result < 0) return VECTOR_ORDER_GT; + if (result > 0) return VECTOR_ORDER_LT; + + return VECTOR_ORDER_EQ; +} + +void test_vector_sort_string() { + vector_result_t res = vector_new(5, sizeof(char*)); + + assert(res.status == VECTOR_OK); + vector_t *v = res.value.vector; + + const char *values[] = { "embedded", "system-programming", "foo", "bar", "hello", "world!" }; + for (size_t idx = 0; idx < 6; idx++) { + vector_push(v, &values[idx]); + } + + vector_result_t sort_res = vector_sort(v, cmp_string_desc); + assert(sort_res.status == VECTOR_OK); + + const char *expected[] = { "world!", "system-programming", "hello", "foo", "embedded", "bar"}; + for (size_t idx = 0; idx < vector_size(v); idx++) { + const char *val = *(const char**)vector_get(v, idx).value.element; + assert(!strcmp(val, expected[idx])); + } + + vector_destroy(v); +} + +// Sort vector with custom data type +typedef struct { + char name[256]; + int age; +} Person; + +vector_order_t cmp_person_by_age(const void *x, const void *y) { + const Person *x_person = (const Person*)x; + const Person *y_person = (const Person*)y; + + if (x_person->age < y_person->age) return VECTOR_ORDER_LT; + if (x_person->age > y_person->age) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +vector_order_t cmp_person_by_name(const void *x, const void *y) { + const Person *x_person = (const Person*)x; + const Person *y_person = (const Person*)y; + + const int result = strcmp(x_person->name, y_person->name); + + if(result < 0) return VECTOR_ORDER_LT; + if(result > 0) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +void test_vector_sort_struct_by_age() { + vector_result_t res = vector_new(5, sizeof(Person)); + + assert(res.status == VECTOR_OK); + vector_t *people = res.value.vector; + + Person p1 = { .name = "Bob", .age = 45 }; + Person p2 = { .name = "Alice", .age = 28 }; + Person p3 = { .name = "Marco", .age = 25 }; + + vector_push(people, &p1); + vector_push(people, &p2); + vector_push(people, &p3); + + vector_result_t sort_res = vector_sort(people, cmp_person_by_age); + assert(sort_res.status == VECTOR_OK); + + Person expected[] = { + { .name = "Marco", .age = 25 }, + { .name = "Alice", .age = 28 }, + { .name = "Bob", .age = 45 } + }; + + for (size_t idx = 0; idx < vector_size(people); idx++) { + Person *p = (Person*)vector_get(people, idx).value.element; + assert(!strcmp(p->name, expected[idx].name)); + assert(p->age == expected[idx].age); + } + + vector_destroy(people); +} + +void test_vector_sort_struct_by_name() { + vector_result_t res = vector_new(5, sizeof(Person)); + + assert(res.status == VECTOR_OK); + vector_t *people = res.value.vector; + + Person p1 = { .name = "Sophia", .age = 45 }; + Person p2 = { .name = "Robert", .age = 28 }; + Person p3 = { .name = "Barbara", .age = 25 }; + Person p4 = { .name = "Christopher", .age = 65 }; + Person p5 = { .name = "Paul", .age = 53 }; + + vector_push(people, &p1); + vector_push(people, &p2); + vector_push(people, &p3); + vector_push(people, &p4); + vector_push(people, &p5); + + vector_result_t sort_res = vector_sort(people, cmp_person_by_name); + assert(sort_res.status == VECTOR_OK); + + Person expected[] = { + { .name = "Barbara", .age = 25 }, + { .name = "Christopher", .age = 65 }, + { .name = "Paul", .age = 53 }, + { .name = "Robert", .age = 28 }, + { .name = "Sophia", .age = 45 } + }; + + for (size_t idx = 0; idx < vector_size(people); idx++) { + Person *p = (Person*)vector_get(people, idx).value.element; + assert(!strcmp(p->name, expected[idx].name)); + assert(p->age == expected[idx].age); + } + + vector_destroy(people); +} + // Set vector element void test_vector_set() { vector_result_t res = vector_new(5, sizeof(int)); @@ -123,7 +323,7 @@ void test_vector_set() { vector_destroy(v); } -// Set vector elemement out of bounds +// Set vector element out of bounds void test_vector_set_ofb() { vector_result_t res = vector_new(5, sizeof(int)); @@ -278,6 +478,11 @@ int main(void) { TEST(vector_push_realloc); TEST(vector_get); TEST(vector_get_ofb); + TEST(vector_sort_int_asc); + TEST(vector_sort_int_desc); + TEST(vector_sort_string); + TEST(vector_sort_struct_by_age); + TEST(vector_sort_struct_by_name); TEST(vector_set); TEST(vector_set_ofb); TEST(vector_pop); diff --git a/usage.c b/usage.c index d204cbf..3454724 100644 --- a/usage.c +++ b/usage.c @@ -23,6 +23,7 @@ static int vector_usage(); static int map_usage(); +static vector_order_t cmp_int_asc(const void *x, const void *y); int main(void) { int st; @@ -38,6 +39,20 @@ int main(void) { 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; + + if (x_int < y_int) return VECTOR_ORDER_LT; + if (x_int > y_int) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +vector_order_t cmp_int_desc(const void *x, const void *y) { + return cmp_int_asc(y, x); +} + int vector_usage() { // Create a vector of 5 integers vector_result_t res = vector_new(5, sizeof(int)); @@ -100,10 +115,80 @@ int vector_usage() { printf("Vector cleared (size should be 0): %zu\n\n", vector_size(vector)); } + // Sort vector in ascending order + int values[] = {5, 10, -9, 3, 1, 0, 4}; + for (size_t idx = 0; idx < 7; idx++) { + vector_result_t sort_push_res = vector_push(vector, &values[idx]); + if (sort_push_res.status != VECTOR_OK) { + printf("Error while adding elements: %s\n", sort_push_res.message); + + return 1; + } + } + + printf("Added new elements. Before sort: "); + for (size_t idx = 0; idx < vector_size(vector); idx++) { + vector_result_t sort_get_res = vector_get(vector, idx); + if (sort_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); + + return 1; + } else { + const int *val = (int*)sort_get_res.value.element; + printf("%d ", *val); + } + } + printf("\n"); + + vector_result_t sort_asc_res = vector_sort(vector, cmp_int_asc); + if (sort_asc_res.status != VECTOR_OK) { + printf("Cannot sort array: %s\n", sort_asc_res.message); + + return 1; + } + + printf("After sort in ascending order: "); + for (size_t idx = 0; idx < vector_size(vector); idx++) { + vector_result_t sort_get_res = vector_get(vector, idx); + if (sort_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); + + return 1; + } else { + int *val = (int*)sort_get_res.value.element; + printf("%d ", *val); + } + } + printf("\n"); + + // Sort vector in descending order + vector_result_t sort_desc_res = vector_sort(vector, cmp_int_desc); + if (sort_desc_res.status != VECTOR_OK) { + printf("Cannot sort array: %s\n", sort_desc_res.message); + + return 1; + } + + printf("After sort in descending order: "); + for (size_t idx = 0; idx < vector_size(vector); idx++) { + vector_result_t sort_get_res = vector_get(vector, idx); + if (sort_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); + + return 1; + } else { + int *val = (int*)sort_get_res.value.element; + printf("%d ", *val); + } + } + printf("\n\n"); + // Free vector vector_result_t del_res = vector_destroy(vector); if (del_res.status != VECTOR_OK) { printf("Error while destroying the vector: %s\n", del_res.message); + + return 1; } return 0;