From 68fee5b7665362fd9077d4be51beb82d2d4d1c97 Mon Sep 17 00:00:00 2001 From: Ivar Fatland Date: Sat, 13 Dec 2025 15:02:25 +0100 Subject: [PATCH] improve dynamic arrays --- cig.h | 34 +++--- dyn_array.c | 82 +++++++++------ test_arena_allocator.c | 32 +++--- test_dynamic_array.c | 229 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 315 insertions(+), 62 deletions(-) diff --git a/cig.h b/cig.h index 37d35b2..adb705d 100644 --- a/cig.h +++ b/cig.h @@ -161,28 +161,37 @@ size_t arr_cap(void *this); #define arr_pop(THIS) (arr_shrink(THIS, 1), THIS[arr_len(THIS)]) - -/* Compile-time assert that works in expression contexts */ #define STATIC_ASSERT(expr) ((void)sizeof(char[(expr) ? 1 : -1])) - bool dyn_array_contains_func(void *this, uint8_t *value); -/* - arr_contains(THIS, TYPE, VALUE) - - - SIZE CHECK: compile-time check that sizeof(*THIS) == sizeof(TYPE) - - TYPE is the type of the value to search for - - VALUE is stored in a compound literal of TYPE - - Pure C99, no extensions needed - - Callers with typeof support can make wrapper macros -*/ #define arr_contains(THIS, ...)\ (\ STATIC_ASSERT(sizeof(*(THIS)) == sizeof(*(__VA_ARGS__))),\ dyn_array_contains_func((THIS), (uint8_t*)(__VA_ARGS__))\ ) +// Comparison function: returns true if a equals b, false otherwise +typedef bool (*dyn_array_eq_fn)(const void *a, const void *b); + +bool dyn_array_contains_cmp_func(void *this, uint8_t *value, dyn_array_eq_fn eq); + +#define arr_contains_cmp(THIS, EQ_FN, ...)\ + (\ + STATIC_ASSERT(sizeof(*(THIS)) == sizeof(*(__VA_ARGS__))),\ + dyn_array_contains_cmp_func((THIS), (uint8_t*)(__VA_ARGS__), (EQ_FN))\ + ) + +// Comparison function for sorting: returns -1 if a < b, 0 if a == b, 1 if a > b +typedef int (*dyn_array_cmp_fn)(const void *a, const void *b); + +void dyn_array_insert_sorted_func(void **this_ptr, const void *value, dyn_array_cmp_fn cmp, const char *file, int line); + +#define arr_insert_sorted(THIS, VALUE, CMP_FN) do { \ + typeof((THIS)[0]) _tmp_value = (VALUE); \ + dyn_array_insert_sorted_func((void**)&(THIS), &_tmp_value, (CMP_FN), __FILE__, __LINE__); \ +} while(0) + // CLI ///////////////////////////////////////////////////////////////////////// #define CLI_UNIQUE1 __macro_internal_34bba35b8b9b20a75f9881e3795630e25d36e620d9c9741e2e9141ba82ec6ef7__ @@ -343,4 +352,5 @@ void allocator_reset(allocator_t this) { #include "string_builder.c" #endif // CIG_IMPL + #endif // CIG_H diff --git a/dyn_array.c b/dyn_array.c index fd8bcd9..32ecd28 100644 --- a/dyn_array.c +++ b/dyn_array.c @@ -1,38 +1,7 @@ #include "cig.h" #include -typedef struct dyn_array_create_non_crashing_func_args { - allocator_t allocator; - size_t itemsize; - size_t initial_capacity; - const char *file; - size_t line; -} dyn_array_create_non_crashing_func_args_t; - - -static inline void *todo_remove_create_func(dyn_array_create_non_crashing_func_args_t args); void *dyn_array_create_func(dyn_array_create_func_args_t args) { - void *bytes = todo_remove_create_func( - (dyn_array_create_non_crashing_func_args_t) { - .allocator=args.allocator, - .itemsize=args.itemsize, - .initial_capacity=args.initial_capacity, - .file=args.file, - .line=args.line - } - ); - return bytes; -} - -static inline void *todo_remove_grow_func(void *this, size_t n_new_items, const char *file, int line); -void *dyn_array_grow_func(void *this, size_t n_new_items, const char *file, int line) { - void *bytes = todo_remove_grow_func(this, n_new_items, file, line); - return bytes; -} - -// These functions are from when there were non-crashing versions of these -// allocating functions. -static inline void *todo_remove_create_func(dyn_array_create_non_crashing_func_args_t args) { dyn_array_header_t *header = allocator_alloc_func( args.allocator, sizeof(dyn_array_header_t) + args.itemsize * args.initial_capacity, @@ -46,7 +15,7 @@ static inline void *todo_remove_create_func(dyn_array_create_non_crashing_func_a return &header->bytes; } -static inline void *todo_remove_grow_func(void *this, size_t n_new_items, const char *file, int line) { +void *dyn_array_grow_func(void *this, size_t n_new_items, const char *file, int line) { dyn_array_header_t *header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); size_t new_size = header->n_items + n_new_items; @@ -69,6 +38,7 @@ static inline void *todo_remove_grow_func(void *this, size_t n_new_items, const return &header->bytes; } + void dyn_array_shrink_func(void *this, size_t n_items_to_remove, const char *file, int line) { if (arr_len(this) < n_items_to_remove) { fprintf( @@ -112,3 +82,51 @@ bool dyn_array_contains_func(void *this, uint8_t *value) { } return false; } + +bool dyn_array_contains_cmp_func(void *this, uint8_t *value, dyn_array_eq_fn eq) { + dyn_array_header_t *header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); + size_t itemsize = header->itemsize; + + for (size_t i = 0; i < header->n_items; i++) { + void *element = &header->bytes[i * itemsize]; + if (eq(element, value)) { + return true; + } + } + return false; +} + +void dyn_array_insert_sorted_func(void **this_ptr, const void *value, dyn_array_cmp_fn cmp, const char *file, int line) { + void *this = *this_ptr; + dyn_array_header_t *header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); + + // First append the value + this = dyn_array_grow_func(this, 1, file, line); + *this_ptr = this; + header = PTR_FROM_FIELD_PTR(dyn_array_header_t, bytes, this); + + size_t itemsize = header->itemsize; + size_t n_items = header->n_items; + + // Copy value to the end + memcpy(&header->bytes[(n_items - 1) * itemsize], value, itemsize); + + // Bubble it into the correct position from the back + size_t pos = n_items - 1; + while (pos > 0) { + void *a = &header->bytes[(pos - 1) * itemsize]; + void *b = &header->bytes[pos * itemsize]; + + if (cmp(a, b) <= 0) { + break; // Already in correct position + } + + // Swap using a temporary buffer + uint8_t temp[itemsize]; + memcpy(temp, a, itemsize); + memcpy(a, b, itemsize); + memcpy(b, temp, itemsize); + + pos--; + } +} diff --git a/test_arena_allocator.c b/test_arena_allocator.c index be5a4b3..0ac264e 100644 --- a/test_arena_allocator.c +++ b/test_arena_allocator.c @@ -186,15 +186,15 @@ Test(arena_allocator, dyn_array_resize_pattern) { allocator_t arena = arena_allocator_create(backing, 10 * KB); allocator_reset(arena); - int *arr = dyn_array_create(arena, int); + int *arr = make_arr(arena, int); // Add 100 elements, forcing multiple resizes for (int i = 0; i < 100; i++) { - dyn_array_append(arr, i * 3); + arr_append(arr, i * 3); } // Verify all elements - cr_assert_eq(dyn_array_length(arr), 100); + cr_assert_eq(arr_len(arr), 100); for (int i = 0; i < 100; i++) { cr_assert_eq(arr[i], i * 3, "Expected arr[%d] = %d, got %d", i, i * 3, arr[i]); } @@ -207,32 +207,32 @@ Test(arena_allocator, multiple_arrays_resize) { allocator_t arena = arena_allocator_create(backing, 20 * KB); allocator_reset(arena); - int *arr1 = dyn_array_create(arena, int); - int *arr2 = dyn_array_create(arena, int); + int *arr1 = make_arr(arena, int); + int *arr2 = make_arr(arena, int); // Add to arr1 for (int i = 0; i < 50; i++) { - dyn_array_append(arr1, i); + arr_append(arr1, i); } // Add to arr2 for (int i = 0; i < 30; i++) { - dyn_array_append(arr2, i * 2); + arr_append(arr2, i * 2); } // Add more to arr1 for (int i = 50; i < 100; i++) { - dyn_array_append(arr1, i); + arr_append(arr1, i); } // Verify arr1 - cr_assert_eq(dyn_array_length(arr1), 100); + cr_assert_eq(arr_len(arr1), 100); for (int i = 0; i < 100; i++) { cr_assert_eq(arr1[i], i); } // Verify arr2 - cr_assert_eq(dyn_array_length(arr2), 30); + cr_assert_eq(arr_len(arr2), 30); for (int i = 0; i < 30; i++) { cr_assert_eq(arr2[i], i * 2); } @@ -267,21 +267,21 @@ Test(arena_allocator, nested_structures_with_resize) { allocator_reset(arena); // Create array of int pointers (like dyn_array of dyn_arrays) - int **rows = dyn_array_create(arena, int*); + int **rows = make_arr(arena, int*); // Add 5 rows for (int i = 0; i < 5; i++) { - int *row = dyn_array_create(arena, int); + int *row = make_arr(arena, int); for (int j = 0; j < 10; j++) { - dyn_array_append(row, i * 10 + j); + arr_append(row, i * 10 + j); } - dyn_array_append(rows, row); + arr_append(rows, row); } // Verify data - cr_assert_eq(dyn_array_length(rows), 5); + cr_assert_eq(arr_len(rows), 5); for (int i = 0; i < 5; i++) { - cr_assert_eq(dyn_array_length(rows[i]), 10); + cr_assert_eq(arr_len(rows[i]), 10); for (int j = 0; j < 10; j++) { cr_assert_eq(rows[i][j], i * 10 + j); } diff --git a/test_dynamic_array.c b/test_dynamic_array.c index 3c737ba..e55da38 100644 --- a/test_dynamic_array.c +++ b/test_dynamic_array.c @@ -1,9 +1,23 @@ #include - #include - #include "cig.h" +static int int_cmp(const void *a, const void *b) { + int ia = *(const int*)a; + int ib = *(const int*)b; + if (ia < ib) return -1; + if (ia > ib) return 1; + return 0; +} + +static bool int_eq(const void *a, const void *b) { + return *(const int*)a == *(const int*)b; +} + +static int reverse_cmp(const void *a, const void *b) { + return -int_cmp(a, b); +} + Test(dynamic_arrays, append) { with_borrow(alloc) { int *numbers = make_arr(alloc, int); @@ -68,3 +82,214 @@ Test(dynamic_arrays, contains) { cr_assert_eq(arr_len(numbers), 100); } } +Test(dynamic_arrays, insert_sorted_ascending) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + // Insert in random order + arr_insert_sorted(arr, 5, int_cmp); + arr_insert_sorted(arr, 2, int_cmp); + arr_insert_sorted(arr, 8, int_cmp); + arr_insert_sorted(arr, 1, int_cmp); + arr_insert_sorted(arr, 9, int_cmp); + arr_insert_sorted(arr, 3, int_cmp); + + // Verify sorted order + cr_assert_eq(arr_len(arr), 6); + cr_assert_eq(arr[0], 1); + cr_assert_eq(arr[1], 2); + cr_assert_eq(arr[2], 3); + cr_assert_eq(arr[3], 5); + cr_assert_eq(arr[4], 8); + cr_assert_eq(arr[5], 9); + } +} + +Test(dynamic_arrays, insert_sorted_descending) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + // Insert in random order with reverse comparison + arr_insert_sorted(arr, 5, reverse_cmp); + arr_insert_sorted(arr, 2, reverse_cmp); + arr_insert_sorted(arr, 8, reverse_cmp); + arr_insert_sorted(arr, 1, reverse_cmp); + + // Verify descending order + cr_assert_eq(arr_len(arr), 4); + cr_assert_eq(arr[0], 8); + cr_assert_eq(arr[1], 5); + cr_assert_eq(arr[2], 2); + cr_assert_eq(arr[3], 1); + } +} + +Test(dynamic_arrays, insert_sorted_duplicates) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + // Insert duplicates + arr_insert_sorted(arr, 5, int_cmp); + arr_insert_sorted(arr, 3, int_cmp); + arr_insert_sorted(arr, 5, int_cmp); + arr_insert_sorted(arr, 3, int_cmp); + arr_insert_sorted(arr, 5, int_cmp); + + // Verify all elements are present + cr_assert_eq(arr_len(arr), 5); + cr_assert_eq(arr[0], 3); + cr_assert_eq(arr[1], 3); + cr_assert_eq(arr[2], 5); + cr_assert_eq(arr[3], 5); + cr_assert_eq(arr[4], 5); + } +} + +Test(dynamic_arrays, contains_found) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + arr_append(arr, 10); + arr_append(arr, 20); + arr_append(arr, 30); + + cr_assert(arr_contains(arr, &(int){10})); + cr_assert(arr_contains(arr, &(int){20})); + cr_assert(arr_contains(arr, &(int){30})); + } +} + +Test(dynamic_arrays, contains_not_found) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + arr_append(arr, 10); + arr_append(arr, 20); + arr_append(arr, 30); + + cr_assert_not(arr_contains(arr, &(int){5})); + cr_assert_not(arr_contains(arr, &(int){15})); + cr_assert_not(arr_contains(arr, &(int){99})); + } +} + +Test(dynamic_arrays, contains_empty) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + cr_assert_not(arr_contains(arr, &(int){10})); + } +} + +Test(dynamic_arrays, insert_sorted_single_element) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + arr_insert_sorted(arr, 42, int_cmp); + + cr_assert_eq(arr_len(arr), 1); + cr_assert_eq(arr[0], 42); + } +} + +Test(dynamic_arrays, insert_sorted_already_sorted) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + // Insert in already sorted order + arr_insert_sorted(arr, 1, int_cmp); + arr_insert_sorted(arr, 2, int_cmp); + arr_insert_sorted(arr, 3, int_cmp); + arr_insert_sorted(arr, 4, int_cmp); + + // Verify order maintained + cr_assert_eq(arr_len(arr), 4); + for (size_t i = 0; i < arr_len(arr); i++) { + cr_assert_eq(arr[i], (int)(i + 1)); + } + } +} + +Test(dynamic_arrays, insert_sorted_reverse_order) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + // Insert in reverse order (worst case for bubble sort) + arr_insert_sorted(arr, 5, int_cmp); + arr_insert_sorted(arr, 4, int_cmp); + arr_insert_sorted(arr, 3, int_cmp); + arr_insert_sorted(arr, 2, int_cmp); + arr_insert_sorted(arr, 1, int_cmp); + + // Verify correct ascending order + cr_assert_eq(arr_len(arr), 5); + for (size_t i = 0; i < arr_len(arr); i++) { + cr_assert_eq(arr[i], (int)(i + 1)); + } + } +} + +Test(dynamic_arrays, contains_cmp_found) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + arr_append(arr, 10); + arr_append(arr, 20); + arr_append(arr, 30); + + cr_assert(arr_contains_cmp(arr, int_eq, &(int){10})); + cr_assert(arr_contains_cmp(arr, int_eq, &(int){20})); + cr_assert(arr_contains_cmp(arr, int_eq, &(int){30})); + } +} + +Test(dynamic_arrays, contains_cmp_not_found) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + arr_append(arr, 10); + arr_append(arr, 20); + arr_append(arr, 30); + + cr_assert_not(arr_contains_cmp(arr, int_eq, &(int){5})); + cr_assert_not(arr_contains_cmp(arr, int_eq, &(int){15})); + cr_assert_not(arr_contains_cmp(arr, int_eq, &(int){99})); + } +} + +Test(dynamic_arrays, contains_cmp_empty) { + with_borrow(alloc) { + int *arr = make_arr(alloc, int, .initial_capacity = 5); + + cr_assert_not(arr_contains_cmp(arr, int_eq, &(int){10})); + } +} + +// Test using arr_contains_cmp for a simple key-value map +typedef struct { + int key; + const char *value; +} kv_pair_t; + +static bool kv_eq_by_key(const void *a, const void *b) { + const kv_pair_t *pa = a; + const kv_pair_t *pb = b; + return pa->key == pb->key; +} + +Test(dynamic_arrays, contains_cmp_key_value_map) { + with_borrow(alloc) { + kv_pair_t *map = make_arr(alloc, kv_pair_t, .initial_capacity = 10); + + // Add some key-value pairs + arr_append(map, ((kv_pair_t){.key = 1, .value = "one"})); + arr_append(map, ((kv_pair_t){.key = 5, .value = "five"})); + arr_append(map, ((kv_pair_t){.key = 10, .value = "ten"})); + + // Check if keys exist + cr_assert(arr_contains_cmp(map, kv_eq_by_key, &(kv_pair_t){.key = 1})); + cr_assert(arr_contains_cmp(map, kv_eq_by_key, &(kv_pair_t){.key = 5})); + cr_assert(arr_contains_cmp(map, kv_eq_by_key, &(kv_pair_t){.key = 10})); + + // Check for non-existent keys + cr_assert_not(arr_contains_cmp(map, kv_eq_by_key, &(kv_pair_t){.key = 2})); + cr_assert_not(arr_contains_cmp(map, kv_eq_by_key, &(kv_pair_t){.key = 99})); + } +} +