From: David Herrmann Date: Wed, 2 Dec 2015 14:08:24 +0000 (+0100) Subject: crbtree: add traversal helpers X-Git-Url: https://git.zarvox.org/shortlog/month?a=commitdiff_plain;h=fe9f0c997bac535ba5265d583113000038e2f9cc;p=crbtree.git crbtree: add traversal helpers Add three new static-inline helpers to the rbtree API. They implement tree-traversal, based on a comparison-function. They differ in the the pointer type they return. These helpers are in no way mandatory, but rather examples how to code insertion and lookup functions. An elaborate test is added for the new code. Signed-off-by: David Herrmann --- diff --git a/.gitignore b/.gitignore index 8069b6f..394b6fe 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ /stamp-* /test-api /test-basic +/test-map diff --git a/Makefile.am b/Makefile.am index a460a7f..83e75f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -105,6 +105,18 @@ test_basic_SOURCES = \ test_basic_LDADD = \ libcrbtree-private.la +# ------------------------------------------------------------------------------ +# test-map + +default_tests += \ + test-map + +test_map_SOURCES = \ + src/test-map.c + +test_map_LDADD = \ + libcrbtree-private.la + # ------------------------------------------------------------------------------ # test suite diff --git a/src/crbtree.h b/src/crbtree.h index d94531c..b80364a 100644 --- a/src/crbtree.h +++ b/src/crbtree.h @@ -171,6 +171,127 @@ static inline void c_rbtree_remove_init(CRBTree *t, CRBNode *n) { } } +/** + * CRBCompareFunc - compare a node to a key + * @t: tree where the node is linked to + * @k: key to compare + * @n: node to compare + * + * If you use the tree-traversal helpers (which are optional), you need to + * provide this callback so they can compare nodes in a tree to the key you + * look for. + * + * The tree @t is provided as optional context to this callback. The key you + * look for is provided as @k, the current node that should be compared to is + * provided as @n. This function should work like strcmp(), that is, return -1 + * if @key orders before @n, 0 if both compare equal, and 1 if it orders after + * @n. + */ +typedef int (*CRBCompareFunc) (CRBTree *t, void *k, CRBNode *n); + +/** + * c_rbtree_find_node() - find node + * @t: tree to search through + * @f: comparison function + * @k: key to search for + * + * This searches through @t for a node that compares equal to @k. The function + * @f must be provided by the caller, which is used to compare nodes to @k. See + * the documentation of CRBCompareFunc for details. + * + * If there are multiple entries that compare equal to @k, this will return a + * pseudo-randomly picked node. If you need stable lookup functions for trees + * where duplicate entries are allowed, you better code your own lookup. + * + * Return: Pointer to matching node, or NULL. + */ +static inline CRBNode *c_rbtree_find_node(CRBTree *t, CRBCompareFunc f, const void *k) { + CRBNode *i; + + assert(t); + assert(f); + + i = t->root; + while (i) { + int v = f(t, (void *)k, i); + if (v < 0) + i = i->left; + else if (v > 0) + i = i->right; + else + return i; + } + + return NULL; +} + +/** + * c_rbtree_find_entry() - find entry + * @_t: tree to search through + * @_f: comparison function + * @_k: key to search for + * @_t: type of the structure that embeds the nodes + * @_o: name of the node-member in type @_t + * + * This is very similar to c_rbtree_find_node(), but instead of returning a + * pointer to the CRBNode, it returns a pointer to the surrounding object. This + * object must embed the CRBNode object. The type of the surrounding object + * must be given as @_t, and the name of the embedded CRBNode member as @_o. + * + * See c_rbtree_find_node() for more details. + * + * Return: Pointer to found entry, NULL if not found. + */ +#define c_rbtree_find_entry(_m, _f, _k, _t, _o) \ + ((_t *)(((char *)c_rbtree_find_node((_m), (_f), (_k)) ?: \ + (char *)NULL + offsetof(_t, _o)) - offsetof(_t, _o))) + +/** + * c_rbtree_find_slot() - find slot to insert new node + * @t: tree to search through + * @f: comparison function + * @k: key to search for + * @p: output storage for parent pointer + * + * This searches through @t just like c_rbtree_find_node() does. However, + * instead of returning a pointer to a node that compares equal to @k, this + * searches for a slot to insert a node with key @k. A pointer to the slot is + * returned, and a pointer to the parent of the slot is stored in @p. Both + * can be passed directly to c_rbtree_add(), together with your node to insert. + * + * If there already is a node in the tree, that compares equal to @k, this will + * return NULL and store the conflicting node in @p. In all other cases, + * this will return a pointer (non-NULL) to the empty slot to insert the node + * at. @p will point to the parent node of that slot. + * + * If you want trees that allow duplicate nodes, you better code your own + * insertion function. + * + * Return: Pointer to slot to insert node, or NULL on conflicts. + */ +static inline CRBNode **c_rbtree_find_slot(CRBTree *t, CRBCompareFunc f, const void *k, CRBNode **p) { + CRBNode **i; + + assert(t); + assert(f); + assert(p); + + i = &t->root; + *p = NULL; + while (*i) { + int v = f(t, (void *)k, *i); + *p = *i; + if (v < 0) + i = &(*i)->left; + else if (v > 0) + i = &(*i)->right; + else + return NULL; + } + + return i; +} + #ifdef __cplusplus } #endif diff --git a/src/test-map.c b/src/test-map.c new file mode 100644 index 0000000..d0ad8dc --- /dev/null +++ b/src/test-map.c @@ -0,0 +1,116 @@ +/*** + This file is part of crbtree. See COPYING for details. + + crbtree is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + crbtree is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with crbtree; If not, see . +***/ + +/* + * Tests Map API + * This contains tests for the CRBMap API, which wraps CRBTree but provides + * insertion and lookup helpers. Tests for internal tree consistency are + * skipped here, as other tests already do that. This just tests for the + * extensions that CRBMap provides over CRBTree. + */ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include "crbtree.h" +#include "crbtree-private.h" + +typedef struct { + unsigned long key; + CRBNode rb; +} Node; + +#define node_from_rb(_rb) ((Node *)((char *)(_rb) - offsetof(Node, rb))) + +static int test_compare(CRBTree *t, void *k, CRBNode *n) { + unsigned long key = (unsigned long)k; + Node *node = node_from_rb(n); + + return (key < node->key) ? -1 : (key > node->key) ? 1 : 0; +} + +static void shuffle(Node **nodes, size_t n_memb) { + unsigned int i, j; + Node *t; + + for (i = 0; i < n_memb; ++i) { + j = rand() % n_memb; + t = nodes[j]; + nodes[j] = nodes[i]; + nodes[i] = t; + } +} + +static void test_map(void) { + CRBNode **slot, *p; + CRBTree t = {}; + Node *nodes[2048]; + unsigned long i; + + /* allocate and initialize all nodes */ + for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) { + nodes[i] = malloc(sizeof(*nodes[i])); + assert(nodes[i]); + nodes[i]->key = i; + c_rbnode_init(&nodes[i]->rb); + } + + /* shuffle nodes */ + shuffle(nodes, sizeof(nodes) / sizeof(*nodes)); + + /* add all nodes, and verify that each node is linked */ + for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) { + assert(!c_rbnode_is_linked(&nodes[i]->rb)); + assert(!c_rbtree_find_entry(&t, test_compare, (void *)nodes[i]->key, Node, rb)); + + slot = c_rbtree_find_slot(&t, test_compare, (void *)nodes[i]->key, &p); + assert(slot); + c_rbtree_add(&t, p, slot, &nodes[i]->rb); + + assert(c_rbnode_is_linked(&nodes[i]->rb)); + assert(nodes[i] == c_rbtree_find_entry(&t, test_compare, (void *)nodes[i]->key, Node, rb)); + } + + /* shuffle nodes again */ + shuffle(nodes, sizeof(nodes) / sizeof(*nodes)); + + /* remove all nodes (in different order) */ + for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) { + assert(c_rbnode_is_linked(&nodes[i]->rb)); + assert(nodes[i] == c_rbtree_find_entry(&t, test_compare, (void *)nodes[i]->key, Node, rb)); + + c_rbtree_remove_init(&t, &nodes[i]->rb); + + assert(!c_rbnode_is_linked(&nodes[i]->rb)); + assert(!c_rbtree_find_entry(&t, test_compare, (void *)nodes[i]->key, Node, rb)); + } + + /* free nodes again */ + for (i = 0; i < sizeof(nodes) / sizeof(*nodes); ++i) + free(nodes[i]); +} + +int main(int argc, char **argv) { + /* we want stable tests, so use fixed seed */ + srand(0xdeadbeef); + + test_map(); + return 0; +}