add cx_tree_search() - relates to #165

Thu, 15 Feb 2024 21:54:43 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 15 Feb 2024 21:54:43 +0100
changeset 826
21840975d541
parent 825
3f324ea53152
child 827
13b40a598d16

add cx_tree_search() - relates to #165

src/cx/tree.h file | annotate | diff | comparison | revisions
src/tree.c file | annotate | diff | comparison | revisions
tests/test_tree.c file | annotate | diff | comparison | revisions
     1.1 --- a/src/cx/tree.h	Wed Feb 14 22:32:13 2024 +0100
     1.2 +++ b/src/cx/tree.h	Thu Feb 15 21:54:43 2024 +0100
     1.3 @@ -47,6 +47,8 @@
     1.4   * Links a node to a (new) parent.
     1.5   *
     1.6   * If the node has already a parent, it is unlinked, first.
     1.7 + * If the parent has children already, the node is prepended to the list
     1.8 + * of all currently existing children.
     1.9   *
    1.10   * @param parent the parent node
    1.11   * @param node the node that shall be linked
    1.12 @@ -94,22 +96,59 @@
    1.13   * contains the given \p data or if one of the children might contain
    1.14   * the data.
    1.15   *
    1.16 + * The function should use the returned integer to indicate how close the
    1.17 + * match is, where a negative number means that it does not match at all.
    1.18 + *
    1.19   * For example if a tree stores file path information, a node that is
    1.20   * describing a parent directory of a filename that is searched, shall
    1.21 - * return 1 to indicate that a child node might contain the searched item.
    1.22 - * On the other hand, if the node denotes a path that is not a prefix of
    1.23 - * the searched filename, the function would return -1 to indicate that
    1.24 - * the search does not need to be continued in that branch.
    1.25 + * return a positive number to indicate that a child node might contain the
    1.26 + * searched item. On the other hand, if the node denotes a path that is not a
    1.27 + * prefix of the searched filename, the function would return -1 to indicate
    1.28 + * that * the search does not need to be continued in that branch.
    1.29   *
    1.30   * @param node the node that is currently investigated
    1.31   * @param data the data that is searched for
    1.32   *
    1.33   * @return 0 if the node contains the data,
    1.34 - * 1 if one of the children might contain the data,
    1.35 - * -1 if neither the node, nor the children contains the data
    1.36 + * positive if one of the children might contain the data,
    1.37 + * negative if neither the node, nor the children contains the data
    1.38   */
    1.39  typedef int (*cx_tree_search_func)(void const *node, void const* data);
    1.40  
    1.41 +
    1.42 +/**
    1.43 + * Searches for data in a tree.
    1.44 + *
    1.45 + * When the data cannot be found exactly, the search function might return a
    1.46 + * closest result which might be a good starting point for adding a new node
    1.47 + * to the tree.
    1.48 + *
    1.49 + * Depending on the tree structure it is not necessarily guaranteed that the
    1.50 + * "closest" match is uniquely defined. This function will search for a node
    1.51 + * with the best match according to the \p sfunc (meaning: the return value of
    1.52 + * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
    1.53 + * node matching the criteria is returned.
    1.54 + *
    1.55 + * @param root the root node
    1.56 + * @param data the data to search for
    1.57 + * @param sfunc the search function
    1.58 + * @param result where the result shall be stored
    1.59 + * @param loc_children offset in the node struct for the children linked list
    1.60 + * @param loc_next offset in the node struct for the next pointer
    1.61 + * @return zero if the node was found exactly, positive if a node was found that
    1.62 + * could contain the node (but doesn't right now), negative if the tree does not
    1.63 + * contain any node that might be related to the searched data
    1.64 + */
    1.65 +__attribute__((__nonnull__))
    1.66 +int cx_tree_search(
    1.67 +        void const *root,
    1.68 +        void const *data,
    1.69 +        cx_tree_search_func sfunc,
    1.70 +        void **result,
    1.71 +        ptrdiff_t loc_children,
    1.72 +        ptrdiff_t loc_next
    1.73 +);
    1.74 +
    1.75  #ifdef __cplusplus
    1.76  } // extern "C"
    1.77  #endif
     2.1 --- a/src/tree.c	Wed Feb 14 22:32:13 2024 +0100
     2.2 +++ b/src/tree.c	Thu Feb 15 21:54:43 2024 +0100
     2.3 @@ -28,6 +28,8 @@
     2.4  
     2.5  #include "cx/tree.h"
     2.6  
     2.7 +#include "cx/array_list.h"
     2.8 +
     2.9  #include <assert.h>
    2.10  
    2.11  #define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
    2.12 @@ -85,3 +87,85 @@
    2.13      tree_prev(node) = NULL;
    2.14      tree_next(node) = NULL;
    2.15  }
    2.16 +
    2.17 +int cx_tree_search(
    2.18 +        void const *root,
    2.19 +        void const *data,
    2.20 +        cx_tree_search_func sfunc,
    2.21 +        void **result,
    2.22 +        ptrdiff_t loc_children,
    2.23 +        ptrdiff_t loc_next
    2.24 +) {
    2.25 +    int ret;
    2.26 +    *result = NULL;
    2.27 +
    2.28 +    // shortcut: compare root before doing anything else
    2.29 +    ret = sfunc(root, data);
    2.30 +    if (ret < 0) {
    2.31 +        return ret;
    2.32 +    } else if (ret == 0 || tree_children(root) == NULL) {
    2.33 +        *result = (void*)root;
    2.34 +        return ret;
    2.35 +    }
    2.36 +
    2.37 +    // create a working stack
    2.38 +    size_t work_cap = 32;
    2.39 +    size_t work_size = 0;
    2.40 +    void const **work = malloc(sizeof(void*) * work_cap);
    2.41 +    #define work_add(node) cx_array_add(&work, &work_size, &work_cap, \
    2.42 +        sizeof(void*), &(node), cx_array_default_reallocator)
    2.43 +
    2.44 +    // add the children of root to the working stack
    2.45 +    {
    2.46 +        void *c = tree_children(root);
    2.47 +        while (c != NULL) {
    2.48 +            work_add(c);
    2.49 +            c = tree_next(c);
    2.50 +        }
    2.51 +    }
    2.52 +
    2.53 +    // remember a candidate for adding the data
    2.54 +    // also remember the exact return code from sfunc
    2.55 +    void *candidate = NULL;
    2.56 +    int ret_candidate = -1;
    2.57 +
    2.58 +    // process the working stack
    2.59 +    while (work_size > 0) {
    2.60 +        // pop element
    2.61 +        void const *node = work[--work_size];
    2.62 +
    2.63 +        // apply the search function
    2.64 +        ret = sfunc(node, data);
    2.65 +
    2.66 +        if (ret == 0) {
    2.67 +            // if found, exit the search
    2.68 +            *result = (void*) node;
    2.69 +            work_size = 0;
    2.70 +            break;
    2.71 +        } else if (ret > 0) {
    2.72 +            // if children might contain the data, add them to the stack
    2.73 +            void *c = tree_children(node);
    2.74 +            while (c != NULL) {
    2.75 +                work_add(c);
    2.76 +                c = tree_next(c);
    2.77 +            }
    2.78 +
    2.79 +            // remember this node in case no child is suitable
    2.80 +            if (ret_candidate < 0 || ret < ret_candidate) {
    2.81 +                candidate = (void *) node;
    2.82 +                ret_candidate = ret;
    2.83 +            }
    2.84 +        }
    2.85 +    }
    2.86 +
    2.87 +    // not found, but was there a candidate?
    2.88 +    if (ret != 0 && candidate != NULL) {
    2.89 +        ret = ret_candidate;
    2.90 +        *result = candidate;
    2.91 +    }
    2.92 +
    2.93 +    // free the working queue and return
    2.94 +    #undef workq_add
    2.95 +    free(work);
    2.96 +    return ret;
    2.97 +}
     3.1 --- a/tests/test_tree.c	Wed Feb 14 22:32:13 2024 +0100
     3.2 +++ b/tests/test_tree.c	Thu Feb 15 21:54:43 2024 +0100
     3.3 @@ -42,6 +42,8 @@
     3.4      offsetof(tree_node, parent), offsetof(tree_node, children), \
     3.5      offsetof(tree_node, prev), offsetof(tree_node, next)
     3.6  
     3.7 +#define tree_child_list offsetof(tree_node, children),offsetof(tree_node, next)
     3.8 +
     3.9  CX_TEST(test_tree_link_new_child) {
    3.10      tree_node parent = {0};
    3.11      tree_node child = {0};
    3.12 @@ -160,6 +162,94 @@
    3.13      }
    3.14  }
    3.15  
    3.16 +static int test_tree_search_function(void const *n, void const *d) {
    3.17 +    tree_node const *node = n;
    3.18 +    int data = *((int const*)d);
    3.19 +
    3.20 +    if (data < node->data) return -1;
    3.21 +    else if (data == node->data) return 0;
    3.22 +    else return data - node->data;
    3.23 +}
    3.24 +
    3.25 +CX_TEST(test_tree_search) {
    3.26 +    tree_node root = {0};
    3.27 +    tree_node a = {0};
    3.28 +    tree_node b = {0};
    3.29 +    tree_node c = {0};
    3.30 +    tree_node aa = {0};
    3.31 +    tree_node ab = {0};
    3.32 +    tree_node ba = {0};
    3.33 +    tree_node ca = {0};
    3.34 +    tree_node cb = {0};
    3.35 +    tree_node cc = {0};
    3.36 +    tree_node cba = {0};
    3.37 +
    3.38 +    int testdata[] = {0, 10, 14, 18, 20, 25, 30, 32, 34, 36, 40};
    3.39 +    tree_node* testnodes[] = {&root, &a, &aa, &ab, &b, &ba, &c, &ca, &cb, &cba, &cc};
    3.40 +
    3.41 +    for (unsigned i = 0 ; i <= 10 ; i++) {
    3.42 +        testnodes[i]->data = testdata[i];
    3.43 +    }
    3.44 +
    3.45 +    cx_tree_link(&root, &a, tree_node_layout);
    3.46 +    cx_tree_link(&root, &b, tree_node_layout);
    3.47 +    cx_tree_link(&root, &c, tree_node_layout);
    3.48 +
    3.49 +    cx_tree_link(&a, &aa, tree_node_layout);
    3.50 +    cx_tree_link(&a, &ab, tree_node_layout);
    3.51 +
    3.52 +    cx_tree_link(&b, &ba, tree_node_layout);
    3.53 +
    3.54 +    cx_tree_link(&c, &ca, tree_node_layout);
    3.55 +    cx_tree_link(&c, &cb, tree_node_layout);
    3.56 +    cx_tree_link(&c, &cc, tree_node_layout);
    3.57 +
    3.58 +    cx_tree_link(&cb, &cba, tree_node_layout);
    3.59 +
    3.60 +    int s;
    3.61 +    int r;
    3.62 +    tree_node *n;
    3.63 +    CX_TEST_DO {
    3.64 +        for (unsigned i = 0 ; i <= 10 ; i++) {
    3.65 +            s = testdata[i];
    3.66 +            r = cx_tree_search(&root, &s, test_tree_search_function,
    3.67 +                               (void **) &n, tree_child_list);
    3.68 +            CX_TEST_ASSERT(r == 0);
    3.69 +            CX_TEST_ASSERT(n == testnodes[i]);
    3.70 +        }
    3.71 +
    3.72 +        s = -5;
    3.73 +        r = cx_tree_search(&root, &s, test_tree_search_function,
    3.74 +                           (void **) &n, tree_child_list);
    3.75 +        CX_TEST_ASSERT(r < 0);
    3.76 +        CX_TEST_ASSERT(n == NULL);
    3.77 +
    3.78 +        s = 26;
    3.79 +        r = cx_tree_search(&root, &s, test_tree_search_function,
    3.80 +                           (void **) &n, tree_child_list);
    3.81 +        CX_TEST_ASSERT(r > 0);
    3.82 +        CX_TEST_ASSERT(n == &ba);
    3.83 +
    3.84 +        s = 35;
    3.85 +        r = cx_tree_search(&root, &s, test_tree_search_function,
    3.86 +                           (void **) &n, tree_child_list);
    3.87 +        CX_TEST_ASSERT(r > 0);
    3.88 +        CX_TEST_ASSERT(n == &cb);
    3.89 +
    3.90 +        s = 38;
    3.91 +        r = cx_tree_search(&root, &s, test_tree_search_function,
    3.92 +                           (void **) &n, tree_child_list);
    3.93 +        CX_TEST_ASSERT(r > 0);
    3.94 +        CX_TEST_ASSERT(n == &cba);
    3.95 +
    3.96 +        s = 42;
    3.97 +        r = cx_tree_search(&root, &s, test_tree_search_function,
    3.98 +                           (void **) &n, tree_child_list);
    3.99 +        CX_TEST_ASSERT(r > 0);
   3.100 +        CX_TEST_ASSERT(n == &cc);
   3.101 +    }
   3.102 +}
   3.103 +
   3.104  CxTestSuite *cx_test_suite_tree_low_level(void) {
   3.105      CxTestSuite *suite = cx_test_suite_new("tree (low level)");
   3.106  
   3.107 @@ -167,6 +257,7 @@
   3.108      cx_test_register(suite, test_tree_link_add_child);
   3.109      cx_test_register(suite, test_tree_link_move_to_other_parent);
   3.110      cx_test_register(suite, test_tree_unlink);
   3.111 +    cx_test_register(suite, test_tree_search);
   3.112  
   3.113      return suite;
   3.114  }

mercurial