add cxListEmplace() and cxListEmplaceAt() plus some improvements to the array list implementation default tip

Sun, 04 May 2025 17:22:30 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 04 May 2025 17:22:30 +0200
changeset 1316
c41538edfcef
parent 1315
b4c3e0b4c3d5

add cxListEmplace() and cxListEmplaceAt() plus some improvements to the array list implementation

resolves #665

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/array_list.c file | annotate | diff | comparison | revisions
src/cx/list.h file | annotate | diff | comparison | revisions
src/linked_list.c file | annotate | diff | comparison | revisions
src/list.c file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Sun May 04 12:15:03 2025 +0200
+++ b/CHANGELOG	Sun May 04 17:22:30 2025 +0200
@@ -7,14 +7,17 @@
  * adds cxListFirst() and cxListLast()
  * adds cxListRemoveAndGetFirst() and cxListRemoveAndGetLast(),
    and corresponding macro aliases cxListPopFront() and cxListPop()
+ * adds cxListEmplace() and cxListEmplaceAt()
  * adds cxBufferShrink()
  * adds cxTreeSize()
  * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings
  * adds cx_strcpy() and cx_strcpy_a()
+ * improves performance of the CxList array list implementation
  * changes grow strategy for the mempory pool to reduce reallocations
  * changes grow strategy for CxBuffer, which does now take the page size into account
  * changes the implementation of cx_strreplacen() for improved efficiency
  * changes all cxListIterator() without index to also accept NULL as list argument
+ * changes insert_element member function of CxList to accept NULL source and return a pointer to the inserted element
  * fixes that starting an iteration in a non-root node incorrectly continues iteration with the siblings of that node
  * fixes unnecessary allocations in cx_strcat() family of functions
  * fixes errno value after failing cxBufferSeek() to be consistently EINVAL
--- a/docs/Writerside/topics/about.md	Sun May 04 12:15:03 2025 +0200
+++ b/docs/Writerside/topics/about.md	Sun May 04 17:22:30 2025 +0200
@@ -34,14 +34,17 @@
 * adds cxListFirst() and cxListLast()
 * adds cxListRemoveAndGetFirst() and cxListRemoveAndGetLast(),
   and corresponding macro aliases cxListPopFront() and cxListPop()
+* adds cxListEmplace() and cxListEmplaceAt()
 * adds cxBufferShrink()
 * adds cxTreeSize()
 * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings
 * adds cx_strcpy() and cx_strcpy_a()
+* improves performance of the CxList array list implementation
 * changes grow strategy for the memory pool to reduce reallocations
 * changes grow strategy for CxBuffer, which does now take the page size into account
 * changes the implementation of cx_strreplacen() for improved efficiency
 * changes all cxListIterator() without index to also accept NULL as list argument
+* changes insert_element member function of CxList to accept NULL source and return a pointer to the inserted element
 * fixes that starting an iteration in a non-root node incorrectly continues iteration with the siblings of that node
 * fixes unnecessary allocations in cx_strcat() family of functions
 * fixes errno value after failing cxBufferSeek() to be consistently EINVAL
--- a/docs/Writerside/topics/list.h.md	Sun May 04 12:15:03 2025 +0200
+++ b/docs/Writerside/topics/list.h.md	Sun May 04 17:22:30 2025 +0200
@@ -116,6 +116,10 @@
 
 int cxListInsert(CxList *list, size_t index, const void *elem);
 
+void *cxListEmplace(CxList *list);
+
+void *cxListEmplaceAt(CxList *list, size_t index);
+
 int cxListInsertSorted(CxList *list, const void *elem);
 
 size_t cxListAddArray(CxList *list, const void *array, size_t n);
@@ -132,8 +136,13 @@
 ```
 
 The function `cxListAdd()` appends the element `elem` to the list and returns zero on success or non-zero when allocating the memory for the element fails.
-Similarly, `cxListInsert()` adds the element at the specified `index`,
-and `cxListInsertSorted()` adds the element at the correct position so that the list remains sorted according to the list's compare function.
+Similarly, `cxListInsert()` adds the element at the specified `index`.
+
+The functions `cxListEmplace()` and `cxListEmplaceAt()` behave like `cxListAdd()` and `cxListInsert()`,
+except that they only allocate the memory and return a pointer to it, leaving it to the callee to copy the element data into it.
+Be aware that when the list is storing pointers, the allocated memory will be for the pointer, not the actual element's data.
+
+The function `cxListInsertSorted()` inserts the element at the correct position so that the list remains sorted according to the list's compare function.
 
 When you are currently iterating through a list, you can insert elements before or after the current position of the iterator
 with `cxListInsertBefore()` or `cxListInsertAfter()`, respectively.
@@ -405,7 +414,7 @@
 |------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | `clear`          | Invoke destructor functions on all elements and remove them from the list.                                                                                                                                                                                                                                                 |
 | `deallocate`     | Invoke destructor functions on all elements and deallocate the entire list memory.                                                                                                                                                                                                                                         |
-| `insert_element` | Insert a single element at the specified index. Return zero on success and non-zero on failure.                                                                                                                                                                                                                            |
+| `insert_element` | Insert a single element at the specified index. Return a pointer to the data of the inserted element or `NULL` on failure.                                                                                                                                                                                                 |
 | `insert_array`   | Insert an array of elements starting at the specified index. Return the number of elements inserted.                                                                                                                                                                                                                       |
 | `insert_sorted`  | Insert an array of sorted elements into a sorted list. Return the number of elements inserted.                                                                                                                                                                                                                             |
 | `insert_iter`    | Insert a single element depending on the iterator position. The third argument to this function is zero when the element shall be inserted after the iterator position and non-zero if it shall be inserted before the iterator position. The implementation is also responsible for adjusting the iterator, respectively. |
--- a/src/array_list.c	Sun May 04 12:15:03 2025 +0200
+++ b/src/array_list.c	Sun May 04 17:22:30 2025 +0200
@@ -638,50 +638,38 @@
     // get a correctly typed pointer to the list
     cx_array_list *arl = (cx_array_list *) list;
 
-    // do we need to move some elements?
-    if (index < list->collection.size) {
-        const char *first_to_move = (const char *) arl->data;
-        first_to_move += index * list->collection.elem_size;
-        size_t elems_to_move = list->collection.size - index;
-        size_t start_of_moved = index + n;
-
-        if (cx_array_copy(
-                &arl->data,
-                &list->collection.size,
-                &arl->capacity,
-                0,
-                start_of_moved,
-                first_to_move,
-                list->collection.elem_size,
-                elems_to_move,
-                &arl->reallocator
-        )) {
-            // if moving existing elems is unsuccessful, abort
+    // guarantee enough capacity
+    if (arl->capacity < list->collection.size + n) {
+        size_t new_capacity = list->collection.size + n;
+        new_capacity = new_capacity - (new_capacity % 16) + 16;
+        if (cxReallocateArray(
+                list->collection.allocator,
+                &arl->data, new_capacity,
+                list->collection.elem_size)
+        ) {
             return 0;
         }
+        arl->capacity = new_capacity;
     }
 
-    // note that if we had to move the elements, the following operation
-    // is guaranteed to succeed, because we have the memory already allocated
-    // therefore, it is impossible to leave this function with an invalid array
+    // determine insert position
+    char *arl_data = arl->data;
+    char *insert_pos = arl_data + index * list->collection.elem_size;
 
-    // place the new elements
-    if (cx_array_copy(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            0,
-            index,
-            array,
-            list->collection.elem_size,
-            n,
-            &arl->reallocator
-    )) {
-        // array list implementation is "all or nothing"
-        return 0;
-    } else {
-        return n;
+    // do we need to move some elements?
+    if (index < list->collection.size) {
+        size_t elems_to_move = list->collection.size - index;
+        char *target = insert_pos + n * list->collection.elem_size;
+        memmove(target, insert_pos, elems_to_move * list->collection.elem_size);
     }
+
+    // place the new elements, if any
+    if (array != NULL) {
+        memcpy(insert_pos, array, n * list->collection.elem_size);
+    }
+    list->collection.size += n;
+
+    return n;
 }
 
 static size_t cx_arl_insert_sorted(
@@ -709,12 +697,16 @@
     }
 }
 
-static int cx_arl_insert_element(
+static void *cx_arl_insert_element(
         struct cx_list_s *list,
         size_t index,
         const void *element
 ) {
-    return 1 != cx_arl_insert_array(list, index, element, 1);
+    if (cx_arl_insert_array(list, index, element, 1) == 1) {
+        return ((char*)((cx_array_list *) list)->data) + index * list->collection.elem_size;
+    } else {
+        return NULL;
+    }
 }
 
 static int cx_arl_insert_iter(
@@ -724,26 +716,23 @@
 ) {
     struct cx_list_s *list = iter->src_handle.m;
     if (iter->index < list->collection.size) {
-        int result = cx_arl_insert_element(
-                list,
-                iter->index + 1 - prepend,
-                elem
-        );
-        if (result == 0) {
-            iter->elem_count++;
-            if (prepend != 0) {
-                iter->index++;
-                iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
-            }
+        if (cx_arl_insert_element(list,
+                iter->index + 1 - prepend, elem) == NULL) {
+            return 1;
+        }
+        iter->elem_count++;
+        if (prepend != 0) {
+            iter->index++;
+            iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
         }
-        return result;
+        return 0;
     } else {
-        int result = cx_arl_insert_element(list, list->collection.size, elem);
-        if (result == 0) {
-            iter->elem_count++;
-            iter->index = list->collection.size;
+        if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) {
+            return 1;
         }
-        return result;
+        iter->elem_count++;
+        iter->index = list->collection.size;
+        return 0;
     }
 }
 
--- a/src/cx/list.h	Sun May 04 12:15:03 2025 +0200
+++ b/src/cx/list.h	Sun May 04 17:22:30 2025 +0200
@@ -80,8 +80,10 @@
 
     /**
      * Member function for inserting a single element.
+     * The data pointer may be @c NULL in which case the function shall only allocate memory.
+     * Returns a pointer to the data of the inserted element.
      */
-    int (*insert_element)(
+    void *(*insert_element)(
             struct cx_list_s *list,
             size_t index,
             const void *data
@@ -370,6 +372,7 @@
  * @retval zero success
  * @retval non-zero memory allocation failure
  * @see cxListAddArray()
+ * @see cxListEmplace()
  */
 cx_attr_nonnull
 static inline int cxListAdd(
@@ -377,7 +380,7 @@
         const void *elem
 ) {
     list->collection.sorted = false;
-    return list->cl->insert_element(list, list->collection.size, elem);
+    return list->cl->insert_element(list, list->collection.size, elem) == NULL;
 }
 
 /**
@@ -418,6 +421,7 @@
  * @retval non-zero memory allocation failure or the index is out of bounds
  * @see cxListInsertAfter()
  * @see cxListInsertBefore()
+ * @see cxListEmplaceAt()
  */
 cx_attr_nonnull
 static inline int cxListInsert(
@@ -426,7 +430,41 @@
         const void *elem
 ) {
     list->collection.sorted = false;
-    return list->cl->insert_element(list, index, elem);
+    return list->cl->insert_element(list, index, elem) == NULL;
+}
+
+/**
+ * Allocates memory for an element at the specified index and returns a pointer to that memory.
+ *
+ * @remark When the list is storing pointers, this will return a @c void**.
+ *
+ * @param list the list
+ * @param index the index where to emplace the element
+ * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds
+ * @see cxListEmplace()
+ * @see cxListInsert()
+ */
+cx_attr_nonnull
+static inline void *cxListEmplaceAt(CxList *list, size_t index) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, index, NULL);
+}
+
+
+/**
+ * Allocates memory for an element at the end of the list and returns a pointer to that memory.
+ *
+ * @remark When the list is storing pointers, this will return a @c void**.
+ *
+ * @param list the list
+ * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds
+ * @see cxListEmplaceAt()
+ * @see cxListAdd()
+ */
+cx_attr_nonnull
+static inline void *cxListEmplace(CxList *list) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, list->collection.size, NULL);
 }
 
 /**
--- a/src/linked_list.c	Sun May 04 12:15:03 2025 +0200
+++ b/src/linked_list.c	Sun May 04 17:22:30 2025 +0200
@@ -613,7 +613,9 @@
 
     // initialize new new_node
     new_node->prev = new_node->next = NULL;
-    memcpy(new_node->payload, elem, list->collection.elem_size);
+    if (elem != NULL) {
+        memcpy(new_node->payload, elem, list->collection.elem_size);
+    }
 
     // insert
     cx_linked_list *ll = (cx_linked_list *) list;
@@ -659,12 +661,26 @@
     return n;
 }
 
-static int cx_ll_insert_element(
+static void *cx_ll_insert_element(
         struct cx_list_s *list,
         size_t index,
         const void *element
 ) {
-    return 1 != cx_ll_insert_array(list, index, element, 1);
+    // out-of-bounds check
+    if (index > list->collection.size) return NULL;
+
+    // find position efficiently
+    cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
+
+    // perform first insert
+    if (cx_ll_insert_at(list, node, element)) return NULL;
+
+    // return a pointer to the data of the inserted node
+    if (node == NULL) {
+        return ((cx_linked_list *) list)->begin->payload;
+    } else {
+        return node->next->payload;
+    }
 }
 
 static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;
@@ -1056,12 +1072,12 @@
         }
         return result;
     } else {
-        int result = cx_ll_insert_element(list, list->collection.size, elem);
-        if (result == 0) {
-            iter->elem_count++;
-            iter->index = list->collection.size;
+        if (cx_ll_insert_element(list, list->collection.size, elem) == NULL) {
+            return 1;
         }
-        return result;
+        iter->elem_count++;
+        iter->index = list->collection.size;
+        return 0;
     }
 }
 
--- a/src/list.c	Sun May 04 12:15:03 2025 +0200
+++ b/src/list.c	Sun May 04 17:22:30 2025 +0200
@@ -62,7 +62,7 @@
     list->climpl->deallocate(list);
 }
 
-static int cx_pl_insert_element(
+static void *cx_pl_insert_element(
         struct cx_list_s *list,
         size_t index,
         const void *element
@@ -282,7 +282,7 @@
     const char *src = data;
     size_t i = 0;
     for (; i < n; i++) {
-        if (0 != invoke_list_func(
+        if (NULL == invoke_list_func(
             insert_element, list, index + i,
             src + (i * elem_size))) return i;
     }
@@ -329,7 +329,7 @@
 
         // insert the elements at location si
         if (ins == 1) {
-            if (0 != invoke_list_func(
+            if (NULL == invoke_list_func(
                 insert_element, list, di, src)) return inserted;
         } else {
             size_t r = invoke_list_func(insert_array, list, di, src, ins);
--- a/tests/test_list.c	Sun May 04 12:15:03 2025 +0200
+++ b/tests/test_list.c	Sun May 04 17:22:30 2025 +0200
@@ -1370,6 +1370,63 @@
     CX_TEST_ASSERT(*(int *) cxListAt(list, 3) == 42);
 })
 
+roll_out_test_combos(emplace, {
+    if (isptrlist) {
+        int **x;
+        int y = 5;
+        int z = 7;
+        int w = 13;
+
+        x = cxListEmplace(list);
+        CX_TEST_ASSERT(x != NULL);
+        CX_TEST_ASSERT(cxListSize(list) == 1);
+        *x = &y;
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 0) == 5);
+
+        x = cxListEmplace(list);
+        CX_TEST_ASSERT(x != NULL);
+        CX_TEST_ASSERT(cxListSize(list) == 2);
+        *x = &z;
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 1) == 7);
+
+        CX_TEST_ASSERT(NULL == cxListEmplaceAt(list, 3));
+        CX_TEST_ASSERT(cxListSize(list) == 2);
+
+        x = cxListEmplaceAt(list, 1);
+        CX_TEST_ASSERT(x != NULL);
+        CX_TEST_ASSERT(cxListSize(list) == 3);
+        *x = &w;
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 0) == 5);
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 1) == 13);
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 2) == 7);
+    } else {
+        int *x;
+
+        x = cxListEmplace(list);
+        CX_TEST_ASSERT(x != NULL);
+        CX_TEST_ASSERT(cxListSize(list) == 1);
+        *x = 5;
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 0) == 5);
+
+        x = cxListEmplace(list);
+        CX_TEST_ASSERT(x != NULL);
+        CX_TEST_ASSERT(cxListSize(list) == 2);
+        *x = 7;
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 1) == 7);
+
+        CX_TEST_ASSERT(NULL == cxListEmplaceAt(list, 3));
+        CX_TEST_ASSERT(cxListSize(list) == 2);
+
+        x = cxListEmplaceAt(list, 1);
+        CX_TEST_ASSERT(x != NULL);
+        CX_TEST_ASSERT(cxListSize(list) == 3);
+        *x = 13;
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 0) == 5);
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 1) == 13);
+        CX_TEST_ASSERT(*(int*)cxListAt(list, 2) == 7);
+    }
+})
+
 roll_out_test_combos_with_defaulted_funcs(insert_array, {
     int a[5] = array_init(5, 47, 11, 13, 42);
     int b[5] = array_init(9, 18, 72, 50, 7);
@@ -1988,7 +2045,7 @@
 static CX_TEST_SUBROUTINE(test_list_verify_destructor, CxList *list,
                           const int *testdata, size_t testdata_len) {
     destr_test_ctr = 0;
-    
+
     int off = list->collection.store_pointer ? 1 : 0;
 
     cxListRemove(list, 15);
@@ -2064,6 +2121,8 @@
     cx_test_register(suite, test_list_parl_add);
     cx_test_register(suite, test_list_arl_insert);
     cx_test_register(suite, test_list_parl_insert);
+    cx_test_register(suite, test_list_arl_emplace);
+    cx_test_register(suite, test_list_parl_emplace);
     cx_test_register(suite, test_list_arl_insert_array);
     cx_test_register(suite, test_list_parl_insert_array);
     cx_test_register(suite, test_list_arl_insert_sorted);
@@ -2169,6 +2228,8 @@
     cx_test_register(suite, test_list_pll_add);
     cx_test_register(suite, test_list_ll_insert);
     cx_test_register(suite, test_list_pll_insert);
+    cx_test_register(suite, test_list_ll_emplace);
+    cx_test_register(suite, test_list_pll_emplace);
     cx_test_register(suite, test_list_ll_insert_array);
     cx_test_register(suite, test_list_pll_insert_array);
     cx_test_register(suite, test_list_ll_insert_sorted);

mercurial