add cx_array_binary_search() - fixes #424

Tue, 17 Sep 2024 23:11:17 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 17 Sep 2024 23:11:17 +0200
changeset 884
d375d8056262
parent 883
3012e9b4214e
child 885
878a450e79bd

add cx_array_binary_search() - fixes #424

src/array_list.c file | annotate | diff | comparison | revisions
src/cx/array_list.h file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
--- a/src/array_list.c	Tue Sep 17 19:38:41 2024 +0200
+++ b/src/array_list.c	Tue Sep 17 23:11:17 2024 +0200
@@ -236,6 +236,57 @@
     return CX_ARRAY_SUCCESS;
 }
 
+size_t cx_array_binary_search_inf(
+        void const *arr,
+        size_t size,
+        size_t elem_size,
+        void const *elem,
+        cx_compare_func cmp_func
+) {
+    // declare a variable that will contain the compare results
+    int result;
+
+    // cast the array pointer to something we can use offsets with
+    char const *array = arr;
+
+    // check the first array element
+    result = cmp_func(elem, array);
+    if (result <= 0) {
+        return 0;
+    }
+
+    // check the last array element
+    result = cmp_func(elem, array + elem_size * (size - 1));
+    if (result >= 0) {
+        return size - 1;
+    }
+
+    // the element is now guaranteed to be somewhere in the list
+    // so start the binary search
+    size_t left_index = 1;
+    size_t right_index = size - 1;
+    size_t pivot_index;
+
+    while (left_index <= right_index) {
+        pivot_index = left_index + (right_index - left_index) / 2;
+        char const *arr_elem = array + pivot_index * elem_size;
+        result = cmp_func(elem, arr_elem);
+        if (result == 0) {
+            // found it!
+            return pivot_index;
+        } else if (result < 0) {
+            // element is smaller than pivot, continue search left
+            right_index = pivot_index - 1;
+        } else {
+            // element is larger than pivot, continue search right
+            left_index = pivot_index + 1;
+        }
+    }
+
+    // report the largest upper bound
+    return result < 0 ? (pivot_index - 1) : pivot_index;
+}
+
 #ifndef CX_ARRAY_SWAP_SBO_SIZE
 #define CX_ARRAY_SWAP_SBO_SIZE 128
 #endif
--- a/src/cx/array_list.h	Tue Sep 17 19:38:41 2024 +0200
+++ b/src/cx/array_list.h	Tue Sep 17 23:11:17 2024 +0200
@@ -294,6 +294,67 @@
     cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
         cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator)
 
+
+/**
+ * Searches the largest lower bound in a sorted array.
+ *
+ * In other words, this function returns the index of the largest element
+ * in \p arr that is less or equal to \p elem with respect to \p cmp_func.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the closest element in the array
+ */
+size_t cx_array_binary_search_inf(
+        void const *arr,
+        size_t size,
+        size_t elem_size,
+        void const *elem,
+        cx_compare_func cmp_func
+) __attribute__((__nonnull__));
+
+/**
+ * Searches an item in a sorted array.
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the element in the array, or \p size if the element
+ * cannot be found
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search(
+        void const *arr,
+        size_t size,
+        size_t elem_size,
+        void const *elem,
+        cx_compare_func cmp_func
+) {
+    size_t index = cx_array_binary_search_inf(
+            arr, size, elem_size, elem, cmp_func
+    );
+    char const *found = ((char const *) arr) + index * elem_size;
+    if (cmp_func(found, elem) == 0) {
+        return index;
+    } else {
+        return size;
+    }
+}
+
 /**
  * Swaps two array elements.
  *
--- a/tests/test_list.c	Tue Sep 17 19:38:41 2024 +0200
+++ b/tests/test_list.c	Tue Sep 17 23:11:17 2024 +0200
@@ -146,6 +146,36 @@
     }
 }
 
+CX_TEST(test_array_binary_search) {
+    int array[18] = {
+            40, 50, 51, 52, 54, 56, 57, 58, 60,
+            62, 64, 65, 70, 75, 77, 78, 80, 90
+    };
+
+    CX_TEST_DO {
+        cx_for_n(i, 18) {
+            CX_TEST_ASSERT(i == cx_array_binary_search(array, 18, sizeof(int), &array[i], cx_cmp_int));
+        }
+
+        int s = 58;
+        CX_TEST_ASSERT(7 == cx_array_binary_search_inf(array, 18, sizeof(int), &s, cx_cmp_int));
+        s = 60;
+        CX_TEST_ASSERT(8 == cx_array_binary_search_inf(array, 18, sizeof(int), &s, cx_cmp_int));
+        s = 59;
+        CX_TEST_ASSERT(7 == cx_array_binary_search_inf(array, 18, sizeof(int), &s, cx_cmp_int));
+        CX_TEST_ASSERT(18 == cx_array_binary_search(array, 18, sizeof(int), &s, cx_cmp_int));
+        s = 79;
+        CX_TEST_ASSERT(15 == cx_array_binary_search_inf(array, 18, sizeof(int), &s, cx_cmp_int));
+        CX_TEST_ASSERT(18 == cx_array_binary_search(array, 18, sizeof(int), &s, cx_cmp_int));
+        s = 66;
+        CX_TEST_ASSERT(11 == cx_array_binary_search_inf(array, 18, sizeof(int), &s, cx_cmp_int));
+        CX_TEST_ASSERT(18 == cx_array_binary_search(array, 18, sizeof(int), &s, cx_cmp_int));
+        s = 69;
+        CX_TEST_ASSERT(11 == cx_array_binary_search_inf(array, 18, sizeof(int), &s, cx_cmp_int));
+        CX_TEST_ASSERT(18 == cx_array_binary_search(array, 18, sizeof(int), &s, cx_cmp_int));
+    }
+}
+
 typedef struct node {
     struct node *next;
     struct node *prev;
@@ -1582,6 +1612,7 @@
 
     cx_test_register(suite, test_array_add);
     cx_test_register(suite, test_array_insert_sorted);
+    cx_test_register(suite, test_array_binary_search);
 
     cx_test_register(suite, test_list_arl_create);
     cx_test_register(suite, test_list_arl_create_simple);

mercurial