add default impl for some list member functions

Thu, 29 Aug 2024 21:30:52 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 29 Aug 2024 21:30:52 +0200
changeset 875
ee84ac776cbc
parent 874
cdce47f34d48
child 876
f4ce7df9cff0

add default impl for some list member functions

resolves #419

src/cx/list.h file | annotate | diff | comparison | revisions
src/list.c file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
tests/ucxtest.c file | annotate | diff | comparison | revisions
--- a/src/cx/list.h	Thu Aug 29 20:48:15 2024 +0200
+++ b/src/cx/list.h	Thu Aug 29 21:30:52 2024 +0200
@@ -88,6 +88,7 @@
     /**
      * Member function for inserting multiple elements.
      * Implementors SHOULD see to performant implementations for corner cases.
+     * @see cx_list_default_insert_array()
      */
     size_t (*insert_array)(
             struct cx_list_s *list,
@@ -120,6 +121,7 @@
 
     /**
      * Member function for swapping two elements.
+     * @see cx_list_default_swap()
      */
     int (*swap)(
             struct cx_list_s *list,
@@ -146,11 +148,14 @@
 
     /**
      * Member function for sorting the list in-place.
+     * @see cx_list_default_sort()
      */
     void (*sort)(struct cx_list_s *list);
 
     /**
-     * Member function for comparing this list to another list of the same type.
+     * Optional member function for comparing this list
+     * to another list of the same type.
+     * If set to \c NULL, comparison won't be optimized.
      */
     int (*compare)(
             struct cx_list_s const *list,
@@ -173,6 +178,55 @@
 };
 
 /**
+ * Default implementation of an array insert.
+ *
+ * This function uses the element insert function for each element of the array.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param index the index where to insert the data
+ * @param data a pointer to the array of data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *data,
+        size_t n
+);
+
+/**
+ * Default unoptimized sort implementation.
+ *
+ * This function will copy all data to an array, sort the array with standard
+ * qsort, and then copy the data back to the list memory.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list that shall be sorted
+ */
+__attribute__((__nonnull__))
+void cx_list_default_sort(struct cx_list_s *list);
+
+/**
+ * Default unoptimized swap implementation.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list in which to swap
+ * @param i index of one element
+ * @param j index of the other element
+ */
+__attribute__((__nonnull__))
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
+
+/**
  * Common type for all list implementations.
  */
 typedef struct cx_list_s CxList;
--- a/src/list.c	Thu Aug 29 20:48:15 2024 +0200
+++ b/src/list.c	Thu Aug 29 21:30:52 2024 +0200
@@ -217,14 +217,6 @@
     return -1;
 }
 
-static int cx_emptyl_compare(
-        __attribute__((__unused__)) struct cx_list_s const *list,
-        struct cx_list_s const *other
-) {
-    if (other->collection.size == 0) return 0;
-    return -1;
-}
-
 static bool cx_emptyl_iter_valid(__attribute__((__unused__)) void const *iter) {
     return false;
 }
@@ -252,7 +244,7 @@
         cx_emptyl_at,
         cx_emptyl_find_remove,
         cx_emptyl_noop,
-        cx_emptyl_compare,
+        NULL,
         cx_emptyl_noop,
         cx_emptyl_iterator,
 };
@@ -276,6 +268,79 @@
 
 // </editor-fold>
 
+#define invoke_list_func(name, list, ...) \
+    ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \
+    (list, __VA_ARGS__)
+
+size_t cx_list_default_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *data,
+        size_t n
+) {
+    size_t elem_size = list->collection.elem_size;
+    char const *src = data;
+    size_t i = 0;
+    for (; i < n; i++) {
+        if (0 != invoke_list_func(insert_element,
+                                  list, index + i, src + (i * elem_size))) {
+            return i;
+        }
+    }
+    return i;
+}
+
+void cx_list_default_sort(struct cx_list_s *list) {
+    size_t elem_size = list->collection.elem_size;
+    size_t list_size = list->collection.size;
+    void *tmp = malloc(elem_size * list_size);
+    if (tmp == NULL) abort();
+
+    // copy elements from source array
+    char *loc = tmp;
+    for (size_t i = 0; i < list_size; i++) {
+        void *src = invoke_list_func(at, list, i);
+        memcpy(loc, src, elem_size);
+        loc += elem_size;
+    }
+
+    // qsort
+    qsort(tmp, list_size, elem_size,
+          list->collection.cmpfunc);
+
+    // copy elements back
+    loc = tmp;
+    for (size_t i = 0; i < list_size; i++) {
+        void *dest = invoke_list_func(at, list, i);
+        memcpy(dest, loc, elem_size);
+        loc += elem_size;
+    }
+
+    free(tmp);
+}
+
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) {
+    if (i == j) return 0;
+    if (i >= list->collection.size) return 1;
+    if (j >= list->collection.size) return 1;
+
+    size_t elem_size = list->collection.elem_size;
+
+    void *tmp = malloc(elem_size);
+    if (tmp == NULL) return 1;
+
+    void *ip = invoke_list_func(at, list, i);
+    void *jp = invoke_list_func(at, list, j);
+
+    memcpy(tmp, ip, elem_size);
+    memcpy(ip, jp, elem_size);
+    memcpy(jp, tmp, elem_size);
+
+    free(tmp);
+
+    return 0;
+}
+
 void cxListDestroy(CxList *list) {
     list->cl->destructor(list);
 }
@@ -284,17 +349,25 @@
         CxList const *list,
         CxList const *other
 ) {
-    if (
-        // if one is storing pointers but the other is not
-        (list->collection.store_pointer ^ other->collection.store_pointer) ||
+    bool cannot_optimize = false;
+
+    // if one is storing pointers but the other is not
+    cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer;
+
+    // if one class is wrapped but the other is not
+    cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL);
 
-        // if one class is wrapped but the other is not
-        ((list->climpl == NULL) ^ (other->climpl == NULL)) ||
+    // if the compare functions do not match or both are NULL
+    if (!cannot_optimize) {
+        cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ?
+                                                      list->climpl->compare : list->cl->compare);
+        cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ?
+                                                       other->climpl->compare : other->cl->compare);
+        cannot_optimize |= list_cmp != other_cmp;
+        cannot_optimize |= list_cmp == NULL;
+    }
 
-        // if the resolved compare functions are not the same
-        ((list->climpl != NULL ? list->climpl->compare : list->cl->compare) !=
-         (other->climpl != NULL ? other->climpl->compare : other->cl->compare))
-    ) {
+    if (cannot_optimize) {
         // lists are definitely different - cannot use internal compare function
         if (list->collection.size == other->collection.size) {
             CxIterator left = list->cl->iterator(list, 0, false);
--- a/tests/test_list.c	Thu Aug 29 20:48:15 2024 +0200
+++ b/tests/test_list.c	Thu Aug 29 21:30:52 2024 +0200
@@ -928,9 +928,7 @@
         CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));\
     } \
     cx_testing_allocator_destroy(&talloc);
-#define roll_out_test_combos(name, body) \
-static CX_TEST_SUBROUTINE(test_list_verify_##name, CxList *list, \
-    __attribute__((__unused__)) bool isptrlist) body \
+#define roll_out_test_invokers(name) \
 CX_TEST(test_list_ll_##name) { \
     set_up_combo \
         CxList *list = cxLinkedListCreate(alloc, cx_cmp_int, sizeof(int)); \
@@ -955,6 +953,61 @@
         CX_TEST_CALL_SUBROUTINE(test_list_verify_##name, list, true); \
     tear_down_combo \
 }
+#define roll_out_test_combos(name, body) \
+static CX_TEST_SUBROUTINE(test_list_verify_##name, CxList *list, \
+    __attribute__((__unused__)) bool isptrlist) body \
+roll_out_test_invokers(name)
+
+static void set_default_class_funcs(CxList *list, cx_list_class *defaulted_cl) {
+    cx_list_class const *cl = list->climpl == NULL ? list->cl : list->climpl;
+    memcpy(defaulted_cl, cl, sizeof(cx_list_class));
+    defaulted_cl->insert_array = cx_list_default_insert_array;
+    defaulted_cl->sort = cx_list_default_sort;
+    defaulted_cl->swap = cx_list_default_swap;
+    defaulted_cl->compare = NULL;
+    if (list->climpl == NULL) {
+        list->cl = defaulted_cl;
+    } else {
+        list->climpl = defaulted_cl;
+    }
+}
+
+#define do_set_default_class_funcs(list) \
+    cx_list_class defaulted_cl; \
+    set_default_class_funcs(list, &defaulted_cl)
+#define roll_out_test_combos_with_defaulted_funcs(name, body) \
+static CX_TEST_SUBROUTINE(test_list_verify_##name, CxList *list, \
+    __attribute__((__unused__)) bool isptrlist) body \
+roll_out_test_invokers(name) \
+CX_TEST(test_list_llm_##name) { \
+    set_up_combo \
+        CxList *list = cxLinkedListCreate(alloc, cx_cmp_int, sizeof(int)); \
+        do_set_default_class_funcs(list); \
+        CX_TEST_CALL_SUBROUTINE(test_list_verify_##name, list, false); \
+    tear_down_combo \
+} \
+CX_TEST(test_list_arlm_##name) { \
+    set_up_combo \
+        CxList *list = cxArrayListCreate(alloc, cx_cmp_int, sizeof(int), 8); \
+        do_set_default_class_funcs(list); \
+        CX_TEST_CALL_SUBROUTINE(test_list_verify_##name, list, false); \
+    tear_down_combo \
+} \
+CX_TEST(test_list_pllm_##name) { \
+    set_up_combo \
+        CxList *list = cxLinkedListCreate(alloc, cx_cmp_int, CX_STORE_POINTERS); \
+        do_set_default_class_funcs(list); \
+        CX_TEST_CALL_SUBROUTINE(test_list_verify_##name, list, true); \
+    tear_down_combo \
+} \
+CX_TEST(test_list_parlm_##name) { \
+    set_up_combo \
+        CxList *list = cxArrayListCreate(alloc, cx_cmp_int, CX_STORE_POINTERS, 8); \
+        do_set_default_class_funcs(list); \
+        CX_TEST_CALL_SUBROUTINE(test_list_verify_##name, list, true); \
+    tear_down_combo \
+}
+
 #define array_init(...) {__VA_ARGS__}
 
 static inline int *int_test_data_added_to_list(CxList *list, bool isptrlist, size_t len) {
@@ -1005,7 +1058,7 @@
     CX_TEST_ASSERT(*(int *) cxListAt(list, 3) == 42);
 })
 
-roll_out_test_combos(insert_array, {
+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);
     int *aptr[5];
@@ -1111,7 +1164,7 @@
     free(testdata);
 })
 
-roll_out_test_combos(swap, {
+roll_out_test_combos_with_defaulted_funcs(swap, {
     int original[16] = array_init(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
     int swapped[16] = array_init(8, 4, 14, 3, 1, 5, 9, 12, 0, 6, 11, 10, 7, 15, 2, 13);
 
@@ -1178,7 +1231,7 @@
     free(testdata);
 })
 
-roll_out_test_combos(sort, {
+roll_out_test_combos_with_defaulted_funcs(sort, {
     const size_t testdata_len = 250;
     int *testdata = int_test_data_added_to_list(list, isptrlist, testdata_len);
     int *expected = malloc(testdata_len*sizeof(int));
@@ -1262,28 +1315,34 @@
     CX_TEST_ASSERT(cxIteratorValid(iter));
     CX_TEST_ASSERT(iter.index == 2);
     CX_TEST_ASSERT(*(int *) cxIteratorCurrent(iter) == 2);
+    CX_TEST_ASSERT(iter.elem_count == 5);
     cxListInsertAfter(&iter, &newdata[0]);
     CX_TEST_ASSERT(cxIteratorValid(iter));
     CX_TEST_ASSERT(iter.index == 2);
     CX_TEST_ASSERT(*(int *) cxIteratorCurrent(iter) == 2);
+    CX_TEST_ASSERT(iter.elem_count == 6);
     cxListInsertBefore(&iter, &newdata[1]);
     CX_TEST_ASSERT(cxIteratorValid(iter));
     CX_TEST_ASSERT(iter.index == 3);
     CX_TEST_ASSERT(*(int *) cxIteratorCurrent(iter) == 2);
+    CX_TEST_ASSERT(iter.elem_count == 7);
 
     iter = cxListMutIterator(list);
     cxListInsertBefore(&iter, &newdata[2]);
     CX_TEST_ASSERT(cxIteratorValid(iter));
     CX_TEST_ASSERT(iter.index == 1);
     CX_TEST_ASSERT(*(int *) cxIteratorCurrent(iter) == 0);
+    CX_TEST_ASSERT(iter.elem_count == 8);
     iter = cxListMutIteratorAt(list, cxListSize(list));
     cxListInsertBefore(&iter, &newdata[3]);
     CX_TEST_ASSERT(!cxIteratorValid(iter));
     CX_TEST_ASSERT(iter.index == 9);
+    CX_TEST_ASSERT(iter.elem_count == 9);
     iter = cxListMutIteratorAt(list, cxListSize(list));
     cxListInsertAfter(&iter, &newdata[4]);
     CX_TEST_ASSERT(!cxIteratorValid(iter));
     CX_TEST_ASSERT(iter.index == 10);
+    CX_TEST_ASSERT(iter.elem_count == 10);
 
     int expdata[] = array_init(30, 0, 1, 20, 2, 10, 3, 4, 40, 50);
     cx_for_n (j, 10) CX_TEST_ASSERT(*(int *) cxListAt(list, j) == expdata[j]);
@@ -1336,6 +1395,20 @@
         parl, cxArrayListCreate(cxDefaultAllocator, cx_cmp_int, CX_STORE_POINTERS, 50)
 )
 
+roll_out_test_combos_with_defaulted_funcs(compare_unoptimized, {
+    \
+    const size_t len = 33; \
+    int *testdata = int_test_data_added_to_list(list, isptrlist, len); \
+    CxList *other = cxArrayListCreate(cxDefaultAllocator, cx_cmp_int, sizeof(int), 50); \
+    do_set_default_class_funcs(other); \
+    cx_for_n(i, len)
+    cxListAdd(other, &testdata[i]); \
+    CX_TEST_CALL_SUBROUTINE(test_list_verify_compare, list, other); \
+    cxListDestroy(other); \
+    free(testdata); \
+
+})
+
 static unsigned destr_test_ctr;
 static int destr_last_value;
 
@@ -1463,6 +1536,25 @@
     return suite;
 }
 
+CxTestSuite *cx_test_suite_array_list_defaulted_funcs(void) {
+    CxTestSuite *suite = cx_test_suite_new(
+            "array_list with defaulted functions");
+
+    cx_test_register(suite, test_list_arlm_insert_array);
+    cx_test_register(suite, test_list_parlm_insert_array);
+    cx_test_register(suite, test_list_arlm_swap);
+    cx_test_register(suite, test_list_parlm_swap);
+    cx_test_register(suite, test_list_arlm_sort);
+    cx_test_register(suite, test_list_parlm_sort);
+
+    cx_test_register(suite, test_list_arl_compare_unoptimized);
+    cx_test_register(suite, test_list_parl_compare_unoptimized);
+    cx_test_register(suite, test_list_arlm_compare_unoptimized);
+    cx_test_register(suite, test_list_parlm_compare_unoptimized);
+
+    return suite;
+}
+
 CxTestSuite *cx_test_suite_linked_list(void) {
     CxTestSuite *suite = cx_test_suite_new("linked_list");
 
@@ -1534,6 +1626,25 @@
     return suite;
 }
 
+CxTestSuite *cx_test_suite_linked_list_defaulted_funcs(void) {
+    CxTestSuite *suite = cx_test_suite_new(
+            "linked_list with defaulted functions");
+
+    cx_test_register(suite, test_list_llm_insert_array);
+    cx_test_register(suite, test_list_pllm_insert_array);
+    cx_test_register(suite, test_list_llm_swap);
+    cx_test_register(suite, test_list_pllm_swap);
+    cx_test_register(suite, test_list_llm_sort);
+    cx_test_register(suite, test_list_pllm_sort);
+
+    cx_test_register(suite, test_list_ll_compare_unoptimized);
+    cx_test_register(suite, test_list_pll_compare_unoptimized);
+    cx_test_register(suite, test_list_llm_compare_unoptimized);
+    cx_test_register(suite, test_list_pllm_compare_unoptimized);
+
+    return suite;
+}
+
 CxTestSuite *cx_test_suite_empty_list(void) {
     CxTestSuite *suite = cx_test_suite_new("empty list dummy");
 
--- a/tests/ucxtest.c	Thu Aug 29 20:48:15 2024 +0200
+++ b/tests/ucxtest.c	Thu Aug 29 21:30:52 2024 +0200
@@ -40,6 +40,10 @@
 CxTestSuite *cx_test_suite_empty_list(void);
 CxTestSuite *cx_test_suite_array_list(void);
 CxTestSuite *cx_test_suite_linked_list(void);
+
+CxTestSuite *cx_test_suite_array_list_defaulted_funcs(void);
+
+CxTestSuite *cx_test_suite_linked_list_defaulted_funcs(void);
 CxTestSuite *cx_test_suite_tree_low_level(void);
 CxTestSuite *cx_test_suite_mempool(void);
 CxTestSuite *cx_test_suite_hash_map(void);
@@ -65,6 +69,8 @@
             cx_test_suite_empty_list(),
             cx_test_suite_array_list(),
             cx_test_suite_linked_list(),
+            cx_test_suite_array_list_defaulted_funcs(),
+            cx_test_suite_linked_list_defaulted_funcs(),
             cx_test_suite_tree_low_level(),
             cx_test_suite_mempool(),
             cx_test_suite_hash_map()

mercurial