add convenience functions for easy access to first/last element of a list

Sun, 04 May 2025 12:15:03 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 04 May 2025 12:15:03 +0200
changeset 1315
b4c3e0b4c3d5
parent 1314
25108c15e2d4
child 1316
c41538edfcef

add convenience functions for easy access to first/last element of a list

resolves #666

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/list.h.md file | annotate | diff | comparison | revisions
src/cx/list.h file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
--- 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);

mercurial