Sun, 04 May 2025 12:15:03 +0200
add convenience functions for easy access to first/last element of a list
resolves #666
--- a/CHANGELOG Sun May 04 11:46:13 2025 +0200 +++ b/CHANGELOG Sun May 04 12:15:03 2025 +0200 @@ -4,6 +4,9 @@ * adds cxMempoolTransfer() and cxMempoolTransferObject() * adds cxListSet() * adds cxListContains() + * adds cxListFirst() and cxListLast() + * adds cxListRemoveAndGetFirst() and cxListRemoveAndGetLast(), + and corresponding macro aliases cxListPopFront() and cxListPop() * adds cxBufferShrink() * adds cxTreeSize() * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings
--- a/docs/Writerside/topics/about.md Sun May 04 11:46:13 2025 +0200 +++ b/docs/Writerside/topics/about.md Sun May 04 12:15:03 2025 +0200 @@ -31,6 +31,9 @@ * adds cxMempoolTransfer() and cxMempoolTransferObject() * adds cxListSet() * adds cxListContains() +* adds cxListFirst() and cxListLast() +* adds cxListRemoveAndGetFirst() and cxListRemoveAndGetLast(), + and corresponding macro aliases cxListPopFront() and cxListPop() * adds cxBufferShrink() * adds cxTreeSize() * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings
--- a/docs/Writerside/topics/list.h.md Sun May 04 11:46:13 2025 +0200 +++ b/docs/Writerside/topics/list.h.md Sun May 04 12:15:03 2025 +0200 @@ -162,6 +162,10 @@ void *cxListAt(const CxList *list, size_t index); +void *cxListFirst(const CxList *list); + +void *cxListLast(const CxList *list); + int cxListSet(CxList *list, size_t index, const void *elem); size_t cxListFind(const CxList *list, const void *elem); @@ -179,6 +183,8 @@ If the list is storing pointers (i.e. `cxCollectionStoresPointers()` returns `true`), the pointer at the specified index is directly returned. Otherwise, a pointer to element at the specified index is returned. If the index is out-of-bounds, the function returns `NULL`. +The functions `cxListFirst()` and `cxListLast()` behave like `cxListAt()`, +accessing the first or the last valid index, respectively, and returning `NULL` when the list is empty. The function `cxListSet()` allows you to modify elements at a specific index in the list. This function replaces the element at the specified `index` with the value pointed to by `elem`. @@ -207,12 +213,20 @@ size_t cxListRemoveArray(CxList *list, size_t index, size_t num); +size_t cxListFindRemove(CxList *list, const void *elem); + int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf); +int cxListRemoveAndGetFirst(CxList *list, void *targetbuf); + +int cxListPopFront(CxList *list, void *targetbuf); + +int cxListRemoveAndLast(CxList *list, void *targetbuf); + +int cxListPop(CxList *list, void *targetbuf); + size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf); - -size_t cxListFindRemove(CxList *list, const void *elem); void cxListClear(CxList *list); ``` @@ -222,11 +236,16 @@ The function `cxListRemoveArray()` removes `num` elements starting at `index` and returns the number of elements that have been removed. For each removed element, the [destructor functions](collection.h.md#destructor-functions) are called. -On the other hand, `cxListRemoveAndGet()` and `cxListRemoveArrayAndGet()` do not invoke the destructor functions +The function `cxListFindRemove()` finds the first element within the list that is equivalent to the element pointed to by `elem` and removes it from the list. +This will _also_ call destructor functions, if specified, so take special care when you continue to use `elem`. + +On the other hand, `cxListRemoveAndGet()` family of functions do not invoke the destructor functions and instead copy the elements into the `targetbuf`, which must be large enough to hold the removed elements. -The function `cxListFindRemove()` finds the first element within the list that is equivalent to the element pointed to by `elem` and removes it from the list. -This will _also_ call destructor functions, if specified, so take special care when you continue to use `elem`. +> Note, that when the list was initialized with `CX_STORE_POINTERS`, +> the elements that will be copied to the `targetbuf` are the _pointers_. +> In contrast to other list functions, like `cxListAt()`, an automatic dereferencing does not happen here. +>{style="note"} The function `cxListClear()` simply removes all elements from the list, invoking the destructor functions. It behaves equivalently, but is usually more efficient than calling `cxListRemove()` for every index.
--- a/src/cx/list.h Sun May 04 11:46:13 2025 +0200 +++ b/src/cx/list.h Sun May 04 12:15:03 2025 +0200 @@ -582,8 +582,9 @@ /** * Removes and returns the element at the specified index. * - * No destructor is called and instead the element is copied to the + * No destructor is called, and instead the element is copied to the * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. * * @param list the list * @param index the index of the element @@ -602,11 +603,93 @@ } /** + * Removes and returns the first element of the list. + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list the list + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + * @see cxListPopFront() + * @see cxListRemoveAndGetLast() + */ +cx_attr_nonnull +cx_attr_access_w(2) +static inline int cxListRemoveAndGetFirst( + CxList *list, + void *targetbuf +) { + return list->cl->remove(list, 0, 1, targetbuf) == 0; +} + +/** + * Removes and returns the first element of the list. + * + * Alias for cxListRemoveAndGetFirst(). + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list (@c CxList*) the list + * @param targetbuf (@c void*) a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + * @see cxListRemoveAndGetFirst() + * @see cxListPop() + */ +#define cxListPopFront(list, targetbuf) cxListRemoveAndGetFirst((list), (targetbuf)) + + +/** + * Removes and returns the last element of the list. + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list the list + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + */ +cx_attr_nonnull +cx_attr_access_w(2) +static inline int cxListRemoveAndGetLast( + CxList *list, + void *targetbuf +) { + // note: index may wrap - member function will catch that + return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0; +} + +/** + * Removes and returns the last element of the list. + * + * Alias for cxListRemoveAndGetLast(). + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list (@c CxList*) the list + * @param targetbuf (@c void*) a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + * @see cxListRemoveAndGetLast() + * @see cxListPopFront() + */ +#define cxListPop(list, targetbuf) cxListRemoveAndGetLast((list), (targetbuf)) + +/** * Removes multiple element starting at the specified index. * * If an element destructor function is specified, it is called for each * element. It is guaranteed that the destructor is called before removing - * the element, however, due to possible optimizations it is neither guaranteed + * the element. However, due to possible optimizations, it is neither guaranteed * that the destructors are invoked for all elements before starting to remove * them, nor that the element is removed immediately after the destructor call * before proceeding to the next element. @@ -626,10 +709,11 @@ } /** - * Removes and returns multiple element starting at the specified index. + * Removes and returns multiple elements starting at the specified index. * - * No destructor is called and instead the elements are copied to the + * No destructor is called, and instead the elements are copied to the * @p targetbuf which MUST be large enough to hold all removed elements. + * If the list is storing pointers, @p targetbuf is expected to be an array of pointers. * * @param list the list * @param index the index of the element @@ -665,15 +749,15 @@ /** * Swaps two items in the list. * - * Implementations should only allocate temporary memory for the swap, if + * Implementations should only allocate temporary memory for the swap if * it is necessary. * * @param list the list * @param i the index of the first element * @param j the index of the second element * @retval zero success - * @retval non-zero one of the indices is out of bounds - * or the swap needed extra memory but allocation failed + * @retval non-zero one of the indices is out of bounds, + * or the swap needed extra memory, but allocation failed */ cx_attr_nonnull static inline int cxListSwap( @@ -688,6 +772,8 @@ /** * Returns a pointer to the element at the specified index. * + * If the list is storing pointers, returns the pointer stored at the specified index. + * * @param list the list * @param index the index of the element * @return a pointer to the element or @c NULL if the index is out of bounds @@ -700,6 +786,31 @@ return list->cl->at(list, index); } +/** + * Returns a pointer to the first element. + * + * If the list is storing pointers, returns the first pointer stored in the list. + * + * @param list the list + * @return a pointer to the first element or @c NULL if the list is empty + */ +cx_attr_nonnull +static inline void *cxListFirst(const CxList *list) { + return list->cl->at(list, 0); +} + +/** + * Returns a pointer to the last element. + * + * If the list is storing pointers, returns the last pointer stored in the list. + * + * @param list the list + * @return a pointer to the last element or @c NULL if the list is empty + */ +cx_attr_nonnull +static inline void *cxListLast(const CxList *list) { + return list->cl->at(list, list->collection.size - 1); +} /** * Sets the element at the specified index in the list
--- a/tests/test_list.c Sun May 04 11:46:13 2025 +0200 +++ b/tests/test_list.c Sun May 04 12:15:03 2025 +0200 @@ -1477,6 +1477,63 @@ free(testdata); }) +roll_out_test_combos(remove_and_get, { + int testdata[10]; + for (unsigned i = 0 ; i < 10 ; i++) { + testdata[i] = 2*i; + cxListAdd(list, &testdata[i]); + } + if (isptrlist) { + int *x; + CX_TEST_ASSERT(cxListPop(list, &x) == 0); + CX_TEST_ASSERT(*x == 18); + CX_TEST_ASSERT(cxListPop(list, &x) == 0); + CX_TEST_ASSERT(*x == 16); + CX_TEST_ASSERT(cxListPopFront(list, &x) == 0); + CX_TEST_ASSERT(*x == 0); + CX_TEST_ASSERT(cxListPopFront(list, &x) == 0); + CX_TEST_ASSERT(*x == 2); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 3, &x) == 0); + CX_TEST_ASSERT(*x == 10); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 3, &x) == 0); + CX_TEST_ASSERT(*x == 12); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 8, &x) != 0); + CX_TEST_ASSERT(*x == 12); + + *x = 1337; + cxListClear(list); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 0, &x) != 0); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 1, &x) != 0); + CX_TEST_ASSERT(cxListPop(list, &x) != 0); + CX_TEST_ASSERT(cxListPopFront(list, &x) != 0); + CX_TEST_ASSERT(*x == 1337); + } else { + int x; + CX_TEST_ASSERT(cxListPop(list, &x) == 0); + CX_TEST_ASSERT(x == 18); + CX_TEST_ASSERT(cxListPop(list, &x) == 0); + CX_TEST_ASSERT(x == 16); + CX_TEST_ASSERT(cxListPopFront(list, &x) == 0); + CX_TEST_ASSERT(x == 0); + CX_TEST_ASSERT(cxListPopFront(list, &x) == 0); + CX_TEST_ASSERT(x == 2); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 3, &x) == 0); + CX_TEST_ASSERT(x == 10); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 3, &x) == 0); + CX_TEST_ASSERT(x == 12); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 8, &x) != 0); + CX_TEST_ASSERT(x == 12); + + x = 1337; + cxListClear(list); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 0, &x) != 0); + CX_TEST_ASSERT(cxListRemoveAndGet(list, 1, &x) != 0); + CX_TEST_ASSERT(cxListPop(list, &x) != 0); + CX_TEST_ASSERT(cxListPopFront(list, &x) != 0); + CX_TEST_ASSERT(x == 1337); + } +}) + static unsigned test_remove_array_destr_ctr; static void test_remove_array_destr(cx_attr_unused void *d) { test_remove_array_destr_ctr++; @@ -1544,7 +1601,7 @@ } for (unsigned int i = 4 ; i < 8 ; i++) { if (isptrlist) { - CX_TEST_ASSERT(targetp[i] == 0); + CX_TEST_ASSERT(targetp[i] == NULL); } else { CX_TEST_ASSERT(targete[i] == 0); } @@ -1658,12 +1715,12 @@ // Change the first element int v1new = 99; CX_TEST_ASSERT(cxListSet(list, 0, &v1new) == 0); - CX_TEST_ASSERT(*(int *) cxListAt(list, 0) == 99); + CX_TEST_ASSERT(*(int *) cxListFirst(list) == 99); // Change the last element int v3new = 101; CX_TEST_ASSERT(cxListSet(list, 2, &v3new) == 0); - CX_TEST_ASSERT(*(int *) cxListAt(list, 2) == 101); + CX_TEST_ASSERT(*(int *) cxListLast(list) == 101); // Try index out of bounds int oob = 1337; @@ -2013,6 +2070,8 @@ cx_test_register(suite, test_list_parl_insert_sorted); cx_test_register(suite, test_list_arl_remove); cx_test_register(suite, test_list_parl_remove); + cx_test_register(suite, test_list_arl_remove_and_get); + cx_test_register(suite, test_list_parl_remove_and_get); cx_test_register(suite, test_list_arl_remove_array); cx_test_register(suite, test_list_parl_remove_array); cx_test_register(suite, test_list_arl_find_remove); @@ -2116,6 +2175,8 @@ cx_test_register(suite, test_list_pll_insert_sorted); cx_test_register(suite, test_list_ll_remove); cx_test_register(suite, test_list_pll_remove); + cx_test_register(suite, test_list_ll_remove_and_get); + cx_test_register(suite, test_list_pll_remove_and_get); cx_test_register(suite, test_list_ll_remove_array); cx_test_register(suite, test_list_pll_remove_array); cx_test_register(suite, test_list_ll_find_remove);