Mercurial > libjeffpc
changeset 512:a8855fa73a32
Merge branch trees
author | Josef 'Jeff' Sipek <jeffpc@josefsipek.net> |
---|---|
date | Wed, 25 Jul 2018 14:19:27 -0400 |
parents | 8de8fcdac60e (current diff) 7e39b3094a5f (diff) |
children | 2201b379e2ac |
files | CMakeLists.txt mapfile-vers tests/test_tree_destroy_nodes.c |
diffstat | 15 files changed, 1773 insertions(+), 330 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Jul 25 12:16:02 2018 -0400 +++ b/CMakeLists.txt Wed Jul 25 14:19:27 2018 -0400 @@ -102,6 +102,7 @@ padding.c qstring.c rand.c + rbtree.c scgisvc.c sexpr.c sexpr_dump.c @@ -156,6 +157,7 @@ include/jeffpc/padding.h include/jeffpc/qstring.h include/jeffpc/rand.h + include/jeffpc/rbtree.h include/jeffpc/refcnt.h include/jeffpc/scgi.h include/jeffpc/scgisvc.h
--- a/bst.c Wed Jul 25 12:16:02 2018 -0400 +++ b/bst.c Wed Jul 25 14:19:27 2018 -0400 @@ -62,150 +62,9 @@ return tree_insert_here(&tree->tree, newitem, &cookie->cookie); } -static inline void __bst_swap_nodes(struct tree_tree *tree, - struct tree_node *x, - struct tree_node *y, - const enum tree_dir left) +void bst_remove(struct bst_tree *tree, void *item) { - const enum tree_dir right = 1 - left; - const bool root = tree->root == x; - enum tree_dir dir_to_orig_x; - - ASSERT3P(x, !=, y); - ASSERT3P(y->children[left], ==, NULL); - - if (!root) - dir_to_orig_x = which_dir(x->parent, x); - - if (x->children[right] == y) { - /* - * A A - * \ \ - * \ \ - * x y - * / \ / \ - * / \ => / \ - * B y B x - * \ \ - * \ \ - * E E - */ - struct tree_node *A, *B, *E; - - A = x->parent; - B = x->children[left]; - E = y->children[right]; - - x->parent = y; - y->parent = A; - B->parent = y; - if (E) - E->parent = x; - - x->children[left] = NULL; - x->children[right] = E; - y->children[left] = B; - y->children[right] = x; - if (!root) - A->children[dir_to_orig_x] = y; - else - tree->root = y; - } else { - /* - * A A - * \ \ - * \ \ - * x y - * / \ / \ - * / \ => / \ - * B C B C - * / / - * . . - * . . - * / / - * D D - * / / - * / / - * y x - * \ \ - * \ \ - * E E - */ - struct tree_node *A, *B, *C, *D, *E; + struct tree_node *p, *c; - A = x->parent; - B = x->children[left]; - C = x->children[right]; - D = y->parent; - E = y->children[right]; - - x->parent = D; - y->parent = A; - B->parent = y; - C->parent = y; - if (E) - E->parent = x; - - x->children[left] = NULL; - x->children[right] = E; - y->children[left] = B; - y->children[right] = C; - if (!root) - A->children[dir_to_orig_x] = y; - else - tree->root = y; - D->children[left] = x; - } -} - -static inline void __bst_remove_node(struct tree_tree *tree, - struct tree_node *parent, - struct tree_node *tgt, - struct tree_node *child) -{ - if (parent) - parent->children[which_dir(parent, tgt)] = child; - else - tree->root = child; - - if (child) - child->parent = parent; + tree_remove(&tree->tree, item, &p, &c); } - -void bst_remove(struct bst_tree *_tree, void *item) -{ - struct tree_tree *tree = &_tree->tree; - struct tree_node *node; - - ASSERT3P(tree->root, !=, NULL); - ASSERT3P(item, !=, NULL); - - node = obj2node(tree, item); - - if (node->children[TREE_LEFT] && node->children[TREE_RIGHT]) - /* - * Two children: exchange it with a leaf-ish node greater - * than this node. - */ - __bst_swap_nodes(tree, node, - firstlast(node->children[TREE_RIGHT], TREE_LEFT), - TREE_LEFT); - - /* now, we have zero or one child */ - ASSERT(!node->children[TREE_LEFT] || !node->children[TREE_RIGHT]); - - if (node->children[TREE_LEFT]) - __bst_remove_node(tree, node->parent, node, - node->children[TREE_LEFT]); - else if (node->children[TREE_RIGHT]) - __bst_remove_node(tree, node->parent, node, - node->children[TREE_RIGHT]); - else - __bst_remove_node(tree, node->parent, node, NULL); - - node->parent = NULL; - node->children[TREE_LEFT] = NULL; - node->children[TREE_RIGHT] = NULL; - - tree->num_nodes--; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/jeffpc/rbtree.h Wed Jul 25 14:19:27 2018 -0400 @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017-2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __JEFFPC_RBTREE_H +#define __JEFFPC_RBTREE_H + +#include <stdbool.h> + +#include <jeffpc/tree_private.h> + +struct rb_node { + struct tree_node node; +}; + +struct rb_tree { + struct tree_tree tree; +}; + +struct rb_cookie { + struct tree_cookie cookie; +}; + +#define rb_for_each(tree, pos) \ + for (pos = rb_first(tree); pos; pos = rb_next(tree, pos)) + +extern void rb_create(struct rb_tree *tree, + int (*cmp)(const void *, const void *), + size_t size, size_t off); +extern void rb_destroy(struct rb_tree *tree); + +extern void rb_add(struct rb_tree *tree, void *item); +extern void *rb_insert_here(struct rb_tree *tree, void *item, + struct rb_cookie *cookie); +#define rb_insert(tree, item) rb_insert_here((tree), (item), NULL) +extern void rb_remove(struct rb_tree *tree, void *item); + +static inline bool rb_is_empty(struct rb_tree *tree) +{ + return tree_is_empty(&tree->tree); +} + +static inline size_t rb_numnodes(struct rb_tree *tree) +{ + return tree_numnodes(&tree->tree); +} + +/* + * Search, iteration, and swapping are completely generic + */ +static inline void *rb_find(struct rb_tree *tree, const void *key, + struct rb_cookie *cookie) +{ + return tree_find(&tree->tree, key, &cookie->cookie); +} + +static inline void *rb_first(struct rb_tree *tree) +{ + return tree_first(&tree->tree); +} + +static inline void *rb_last(struct rb_tree *tree) +{ + return tree_last(&tree->tree); +} + +static inline void *rb_next(struct rb_tree *tree, void *item) +{ + return tree_next(&tree->tree, item); +} + +static inline void *rb_prev(struct rb_tree *tree, void *item) +{ + return tree_prev(&tree->tree, item); +} + +static inline void *rb_destroy_nodes(struct rb_tree *tree, + struct rb_cookie *cookie) +{ + return tree_destroy_nodes(&tree->tree, &cookie->cookie); +} + +static inline void rb_swap(struct rb_tree *tree1, struct rb_tree *tree2) +{ + tree_swap(&tree1->tree, &tree2->tree); +} + +#endif
--- a/include/jeffpc/tree_private.h Wed Jul 25 12:16:02 2018 -0400 +++ b/include/jeffpc/tree_private.h Wed Jul 25 14:19:27 2018 -0400 @@ -30,6 +30,7 @@ struct tree_node { struct tree_node *children[2]; struct tree_node *parent; + bool red; }; struct tree_tree { @@ -40,6 +41,7 @@ size_t num_nodes; enum { TREE_FLAVOR_UNBALANCED, + TREE_FLAVOR_RED_BLACK, } flavor; };
--- a/mapfile-vers Wed Jul 25 12:16:02 2018 -0400 +++ b/mapfile-vers Wed Jul 25 14:19:27 2018 -0400 @@ -153,6 +153,13 @@ rand64; rand_buf; + # rbtree + rb_add; + rb_create; + rb_destroy; + rb_insert_here; + rb_remove; + # scgisvc scgisvc;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rbtree.c Wed Jul 25 14:19:27 2018 -0400 @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2017-2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <string.h> + +#include <jeffpc/rbtree.h> +#include <jeffpc/error.h> + +#include "tree_impl.h" + +static inline void set_red(struct tree_node *node, bool red) +{ + if (node) + node->red = red; +} + +static inline bool is_red(struct tree_node *node) +{ + /* NB: NULLs are black by definition */ + return node && node->red; +} + +void rb_create(struct rb_tree *tree, + int (*cmp)(const void *, const void *), + size_t size, size_t off) +{ + ASSERT3U(off + sizeof(struct rb_node), <=, size); + + tree->tree.cmp = cmp; + tree->tree.root = NULL; + tree->tree.node_size = size; + tree->tree.node_off = off; + tree->tree.num_nodes = 0; + tree->tree.flavor = TREE_FLAVOR_RED_BLACK; +} + +void rb_destroy(struct rb_tree *tree) +{ + memset(tree, 0, sizeof(struct rb_tree)); +} + +void rb_add(struct rb_tree *tree, void *item) +{ + struct rb_node *orig; + + orig = rb_insert(tree, item); + if (orig) + panic("%s(%p, %p) failed: tree already contains desired key", + __func__, tree, item); +} + +static void rb_rotate(struct tree_tree *tree, struct tree_node *node, + enum tree_dir left) +{ + const enum tree_dir right = 1 - left; + struct tree_node *tmp; + + ASSERT3P(node->children[right], !=, NULL); + + tmp = node->children[right]; + node->children[right] = tmp->children[left]; + + if (tmp->children[left]) + tmp->children[left]->parent = node; + + tmp->parent = node->parent; + if (!node->parent) { + tree->root = tmp; + } else { + node->parent->children[which_dir(node->parent, node)] = tmp; + } + + tmp->children[left] = node; + node->parent = tmp; +} + +void *rb_insert_here(struct rb_tree *tree, void *newitem, + struct rb_cookie *cookie) +{ + struct tree_node *node; + void *tmp; + + node = obj2node(&tree->tree, newitem); + + /* + * First, insert as if it were an unbalanced binary search tree but + * with the new node marked as red. + */ + tmp = tree_insert_here(&tree->tree, newitem, &cookie->cookie); + if (tmp) + return tmp; + + set_red(node, true); + + /* + * Second, restore red-black tree invariants. + * + * Based on algorithm in CLRS. + */ + while (node && is_red(node)) { + struct tree_node *gparent; + struct tree_node *parent; + struct tree_node *uncle; + + parent = node->parent; + gparent = parent ? parent->parent : NULL; + uncle = gparent ? + gparent->children[1 - which_dir(gparent, parent)] : NULL; + + if (!is_red(parent)) { + break; + } else if (is_red(uncle)) { + set_red(parent, false); + set_red(uncle, false); + set_red(gparent, true); + node = gparent; + } else { + if (which_dir(parent, node) != which_dir(gparent, parent)) { + /* + * G G + * / \ / \ + * / \ / \ + * P U ==> N U + * \ / + * \ / + * N P + * + * This doesn't fix anything, but it sets up + * the tree for the rotation that follows. + */ + rb_rotate(&tree->tree, parent, + 1 - which_dir(parent, node)); + + node = parent; + parent = node->parent; + gparent = parent ? parent->parent : NULL; + } + + if (which_dir(parent, node) == which_dir(gparent, parent)) { + /* + * G P + * / \ / \ + * / \ / \ + * P U ==> N G + * / \ + * / \ + * N U + */ + rb_rotate(&tree->tree, gparent, + 1 - which_dir(parent, node)); + } + + set_red(parent, false); + set_red(gparent, true); + } + } + + set_red(tree->tree.root, false); + + return NULL; /* no previous node */ +} + +void rb_remove(struct rb_tree *_tree, void *item) +{ + struct tree_tree *tree = &_tree->tree; + struct tree_node *parent; + struct tree_node *child; + struct tree_node *node; + + node = obj2node(tree, item); + + tree_remove(tree, item, &parent, &child); + + /* we emptied the tree - no reason to bother fixing up */ + if (!tree->root) + return; + + /* removing a red node cannot violate red-black tree invariants */ + if (is_red(node)) + return; + + node = child; + + while (node != tree->root && !is_red(node)) { + const enum tree_dir left = which_dir(parent, node); + const enum tree_dir right = 1 - left; + struct tree_node *sibling; + + sibling = parent->children[right]; + + if (is_red(sibling)) { + set_red(sibling, false); + set_red(parent, true); + rb_rotate(tree, parent, left); + sibling = parent->children[right]; + } + + if (!is_red(sibling->children[left]) && + !is_red(sibling->children[right])) { + set_red(sibling, true); + node = parent; + } else { + if (!is_red(sibling->children[right])) { + set_red(sibling->children[left], false); + set_red(sibling, true); + rb_rotate(tree, sibling, right); + sibling = parent->children[right]; + } + + set_red(sibling, is_red(parent)); + set_red(parent, false); + set_red(sibling->children[right], false); + rb_rotate(tree, parent, left); + node = tree->root; + } + + parent = node->parent; + } + + set_red(node, false); +}
--- a/tests/.hgignore Wed Jul 25 12:16:02 2018 -0400 +++ b/tests/.hgignore Wed Jul 25 14:19:27 2018 -0400 @@ -18,7 +18,8 @@ test_sexpr_parser test_sexpr_eval test_sexpr_iter -test_tree_destroy_nodes +test_tree_bst +test_tree_rb test_urldecode test_utf32-to-utf8 test_utf8-to-utf32
--- a/tests/CMakeLists.txt Wed Jul 25 12:16:02 2018 -0400 +++ b/tests/CMakeLists.txt Wed Jul 25 14:19:27 2018 -0400 @@ -40,7 +40,8 @@ build_test_bin_and_run(padding) build_test_bin_and_run(sexpr_eval) build_test_bin_and_run(sexpr_iter) -build_test_bin_and_run(tree_destroy_nodes) +build_test_bin_and_run(tree_bst) +build_test_bin_and_run(tree_rb) build_test_bin_and_run(urldecode) build_test_bin_and_run(utf32-to-utf8) build_test_bin_and_run(utf8-to-utf32)
--- a/tests/perf_tree.c Wed Jul 25 12:16:02 2018 -0400 +++ b/tests/perf_tree.c Wed Jul 25 14:19:27 2018 -0400 @@ -23,13 +23,50 @@ #include <jeffpc/jeffpc.h> #include <jeffpc/error.h> #include <jeffpc/bst.h> +#include <jeffpc/rbtree.h> #include <jeffpc/rand.h> #include <jeffpc/time.h> -#define NITERS 100000 +#define NITERS 1000000 + +#define PERF_TREE_RB + +#if defined(PERF_TREE_BST) +/* + * Use fewer iterations to avoid pathological behavior with sequential + * insertions taking O(n^2) with a large n. + */ +#undef NITERS +#define NITERS 100000 +#define TREE_TREE bst_tree +#define TREE_NODE bst_node +#define TREE_COOKIE bst_cookie +#define TREE_CREATE bst_create +#define TREE_DESTROY bst_destroy +#define TREE_FIND bst_find +#define TREE_INSERT bst_insert +#define TREE_REMOVE bst_remove +#define TREE_FOR_EACH bst_for_each +#define TREE_NUMNODES bst_numnodes +#define TREE_DESTROY_NODES bst_destroy_nodes +#elif defined(PERF_TREE_RB) +#define TREE_TREE rb_tree +#define TREE_NODE rb_node +#define TREE_COOKIE rb_cookie +#define TREE_CREATE rb_create +#define TREE_DESTROY rb_destroy +#define TREE_FIND rb_find +#define TREE_INSERT rb_insert +#define TREE_REMOVE rb_remove +#define TREE_FOR_EACH rb_for_each +#define TREE_NUMNODES rb_numnodes +#define TREE_DESTROY_NODES rb_destroy_nodes +#else +#error "Unspecified test type" +#endif struct node { - struct bst_node node; + struct TREE_NODE node; uint32_t v; }; @@ -68,37 +105,37 @@ seq_v = 0; } -static void insert(struct bst_tree *tree, struct node *nodes, +static void insert(struct TREE_TREE *tree, struct node *nodes, uint32_t (*f)(void)) { uint64_t start, end; size_t i, ok; - ASSERT3U(bst_numnodes(tree), ==, 0); + ASSERT3U(TREE_NUMNODES(tree), ==, 0); start = gettick(); for (i = 0, ok = 0; i < NITERS; i++) { nodes[i].v = f(); - if (!bst_insert(tree, &nodes[i])) + if (!TREE_INSERT(tree, &nodes[i])) ok++; } end = gettick(); - ASSERT3U(bst_numnodes(tree), ==, ok); + ASSERT3U(TREE_NUMNODES(tree), ==, ok); cmn_err(CE_INFO, "%zu/%zu insertions in %"PRIu64" ns", ok, NITERS, end - start); } -static void find_or_delete(struct bst_tree *tree, struct node *nodes, +static void find_or_delete(struct TREE_TREE *tree, struct node *nodes, uint32_t (*f)(void), bool find_only) { uint64_t start, end; size_t orig_numnodes; size_t i, ok; - orig_numnodes = bst_numnodes(tree); + orig_numnodes = TREE_NUMNODES(tree); start = gettick(); for (i = 0, ok = 0; i < NITERS * 10; i++) { @@ -107,19 +144,19 @@ }; struct node *node; - node = bst_find(tree, &key, NULL); + node = TREE_FIND(tree, &key, NULL); if (node) { if (!find_only) - bst_remove(tree, node); + TREE_REMOVE(tree, node); ok++; } } end = gettick(); if (find_only) - ASSERT3U(bst_numnodes(tree), ==, orig_numnodes); + ASSERT3U(TREE_NUMNODES(tree), ==, orig_numnodes); else - ASSERT3U(bst_numnodes(tree), ==, orig_numnodes - ok); + ASSERT3U(TREE_NUMNODES(tree), ==, orig_numnodes - ok); cmn_err(CE_INFO, "%zu/%zu %s in %"PRIu64" ns", ok, NITERS, find_only ? "finds" : "deletions", end - start); @@ -127,11 +164,11 @@ static void test(uint32_t (*f)(void), void (*reset)(void)) { - struct bst_tree tree; + struct TREE_TREE tree; struct node *nodes; - bst_create(&tree, cmp, sizeof(struct node), - offsetof(struct node, node)); + TREE_CREATE(&tree, cmp, sizeof(struct node), + offsetof(struct node, node)); nodes = calloc(NITERS, sizeof(struct node)); if (!nodes) @@ -146,7 +183,7 @@ free(nodes); - bst_destroy(&tree); + TREE_DESTROY(&tree); } static void usage(const char *prog) @@ -173,6 +210,9 @@ jeffpc_init(&init_ops); + if (argc < 2) + usage(argv[0]); + for (i = 1; i < argc; i++) { test_name = argv[i];
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_tree_bst.c Wed Jul 25 14:19:27 2018 -0400 @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define TEST_TREE_BST + +#include "test_tree_common.c" + +static struct test2 checks2[2] = { + [true] = { + "ascending", + { &a, NULL, &b }, + { + { &a, { &b } }, + { &b, { &a } }, + }, + }, + [false] = { + "descending", + { &b, &a, NULL }, + { + { &a, { &b } }, + { &b, { &a } }, + }, + }, +}; + +static struct test3 checks3[6] = { + [0] = { + .pre = { &a, + NULL, &b, + NULL, NULL, NULL, &c }, + .sub = { + [0] = { &a, { &b, NULL, &c }, }, + [1] = { &b, { &a, NULL, &c }, }, + [2] = { &c, { &a, NULL, &b }, }, + }, + }, + [1] = { + .pre = { &a, + NULL, &c, + NULL, NULL, &b, NULL }, + .sub = { + [0] = { &a, { &c, &b, NULL }, }, + [1] = { &b, { &a, NULL, &c }, }, + [2] = { &c, { &a, NULL, &b }, }, + }, + }, + [2] = { + .pre = { &b, + &a, &c }, + .sub = { + [0] = { &a, { &b, NULL, &c }, }, + [1] = { &b, { &c, &a, NULL }, }, + [2] = { &c, { &b, &a, NULL }, }, + }, + }, + [3] = { + .pre = { &b, + &a, &c }, + .sub = { + [0] = { &a, { &b, NULL, &c }, }, + [1] = { &b, { &c, &a, NULL }, }, + [2] = { &c, { &b, &a, NULL }, }, + }, + }, + [4] = { + .pre = { &c, + &a, NULL, + NULL, &b, NULL, NULL }, + .sub = { + [0] = { &a, { &c, &b, NULL }, }, + [1] = { &b, { &c, &a, NULL }, }, + [2] = { &c, { &a, NULL, &b }, }, + }, + }, + [5] = { + .pre = { &c, + &b, NULL, + &a, NULL, NULL, NULL }, + .sub = { + [0] = { &a, { &c, &b, NULL }, }, + [1] = { &b, { &c, &a, NULL }, }, + [2] = { &c, { &b, &a, NULL }, }, + }, + }, +}; + +static struct test4 checks4[24] = { + [0] = { + .pre = { &a, + NULL, &b, + NULL, NULL, NULL, &c, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, &d }, + .sub = { + [0] = { &a, + { &b, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [1] = { &b, + { &a, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [2] = { &c, + { &a, + NULL, &b, + NULL, NULL, NULL, &d }, + }, + [3] = { &d, + { &a, + NULL, &b, + NULL, NULL, NULL, &c }, + }, + }, + }, + [1] = { + .pre = { &a, + NULL, &c, + NULL, NULL, &b, &d }, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, + { &a, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [2] = { &c, + { &a, + NULL, &d, + NULL, NULL, &b, NULL }, + }, + [3] = { &d, + { &a, + NULL, &c, + NULL, NULL, &b, NULL }, + }, + }, + }, + [2] = { + .pre = TEST4_BACD, + .sub = { + [0] = { &a, + { &b, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, TEST3_BAD }, + [3] = { &d, TEST3_BAC }, + }, + }, + [3] = { + .pre = TEST4_BACD, + .sub = { + [0] = { &a, + { &b, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, TEST3_BAD }, + [3] = { &d, TEST3_BAC }, + }, + }, + [4] = { + .pre = TEST4_CADB, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, + { &d, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + [3] = { &d, + { &c, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + }, + }, + [5] = { + .pre = TEST4_CBDA, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, + { &d, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + [3] = { &d, + { &c, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + }, + }, + + [6] = { + .pre = { &a, + NULL, &b, + NULL, NULL, NULL, &d, + NULL, NULL, NULL, NULL, NULL, NULL, &c, NULL }, + .sub = { + [0] = { &a, + { &b, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [1] = { &b, + { &a, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [2] = { &c, + { &a, + NULL, &b, + NULL, NULL, NULL, &d }, + }, + [3] = { &d, + { &a, + NULL, &b, + NULL, NULL, NULL, &c }, + }, + }, + }, + [7] = { + .pre = { &a, + NULL, &c, + NULL, NULL, &b, &d }, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, + { &a, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [2] = { &c, + { &a, + NULL, &d, + NULL, NULL, &b, NULL }, + }, + [3] = { &d, + { &a, + NULL, &c, + NULL, NULL, &b, NULL }, + }, + }, + }, + [8] = { + .pre = TEST4_BADC, + .sub = { + [0] = { &a, + { &b, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, TEST3_BAD }, + [3] = { &d, TEST3_BAC }, + }, + }, + [9] = { + .pre = TEST4_BACD, + .sub = { + [0] = { &a, + { &b, + NULL, &c, + NULL, NULL, NULL, &d }, + }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, TEST3_BAD }, + [3] = { &d, TEST3_BAC }, + }, + }, + [10] = { + .pre = TEST4_CADB, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, + { &d, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + [3] = { &d, + { &c, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + }, + }, + [11] = { + .pre = TEST4_CBDA, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, + { &d, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + [3] = { &d, + { &c, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + }, + }, + + [12] = { + .pre = { &a, + NULL, &d, + NULL, NULL, &b, NULL, + NULL, NULL, NULL, NULL, NULL, &c, NULL, NULL }, + .sub = { + [0] = { &a, + { &d, + &b, NULL, + NULL, &c, NULL, NULL }, + }, + [1] = { &b, + { &a, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [2] = { &c, + { &a, + NULL, &d, + NULL, NULL, &b, NULL }, + }, + [3] = { &d, + { &a, + NULL, &b, + NULL, NULL, NULL, &c }, + }, + }, + }, + [13] = { + .pre = { &a, + NULL, &d, + NULL, NULL, &c, NULL, + NULL, NULL, NULL, NULL, &b, NULL, NULL, NULL }, + .sub = { + [0] = { &a, + { &d, + &c, NULL, + &b, NULL, NULL, NULL }, + }, + [1] = { &b, + { &a, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [2] = { &c, + { &a, + NULL, &d, + NULL, NULL, &b, NULL }, + }, + [3] = { &d, + { &a, + NULL, &c, + NULL, NULL, &b, NULL }, + }, + }, + }, + [14] = { + .pre = TEST4_BADC, + .sub = { + [0] = { &a, + { &b, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, TEST3_BAD }, + [3] = { &d, TEST3_BAC }, + }, + }, + [15] = { + .pre = TEST4_BADC, + .sub = { + [0] = { &a, + { &b, + NULL, &d, + NULL, NULL, &c, NULL }, + }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, TEST3_BAD }, + [3] = { &d, TEST3_BAC }, + }, + }, + [16] = { + .pre = TEST4_CADB, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, + { &d, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + [3] = { &d, + { &c, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + }, + }, + [17] = { + .pre = TEST4_CBDA, + .sub = { + [0] = { &a, TEST3_CBD }, + [1] = { &b, TEST3_CAD }, + [2] = { &c, + { &d, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + [3] = { &d, + { &c, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + }, + }, + + [18] = { + .pre = { &d, + &a, NULL, + NULL, &b, NULL, NULL, + NULL, NULL, NULL, &c, NULL, NULL, NULL, NULL }, + .sub = { + [0] = { &a, + { &d, + &b, NULL, + NULL, &c, NULL, NULL }, + }, + [1] = { &b, + { &d, + &a, NULL, + NULL, &c, NULL, NULL }, + }, + [2] = { &c, + { &d, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + [3] = { &d, + { &a, + NULL, &b, + NULL, NULL, NULL, &c }, + }, + }, + }, + [19] = { + .pre = { &d, + &a, NULL, + NULL, &c, NULL, NULL, + NULL, NULL, &b, NULL, NULL, NULL, NULL, NULL }, + .sub = { + [0] = { &a, + { &d, + &c, NULL, + &b, NULL, NULL, NULL }, + }, + [1] = { &b, + { &d, + &a, NULL, + NULL, &c, NULL, NULL }, + }, + [2] = { &c, + { &d, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + [3] = { &d, + { &a, + NULL, &c, + NULL, NULL, &b, NULL }, + }, + }, + }, + [20] = { + .pre = { &d, + &b, NULL, + &a, &c, NULL, NULL}, + .sub = { + [0] = { &a, + { &d, + &b, NULL, + NULL, &c, NULL, NULL }, + }, + [1] = { &b, + { &d, + &c, NULL, + &a, NULL, NULL, NULL }, + }, + [2] = { &c, + { &d, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + [3] = { &d, + { &b, + &a, &c }, + }, + }, + }, + [21] = { + .pre = { &d, + &b, NULL, + &a, &c, NULL, NULL}, + .sub = { + [0] = { &a, + { &d, + &b, NULL, + NULL, &c, NULL, NULL }, + }, + [1] = { &b, + { &d, + &c, NULL, + &a, NULL, NULL, NULL }, + }, + [2] = { &c, + { &d, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + [3] = { &d, TEST3_BAC }, + }, + }, + [22] = { + .pre = { &d, + &c, NULL, + &a, NULL, NULL, NULL, + NULL, &b, NULL, NULL, NULL, NULL, NULL, NULL }, + .sub = { + [0] = { &a, + { &d, + &c, NULL, + &b, NULL, NULL, NULL }, + }, + [1] = { &b, + { &d, + &c, NULL, + &a, NULL, NULL, NULL }, + }, + [2] = { &c, + { &d, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + [3] = { &d, + { &c, + &a, NULL, + NULL, &b, NULL, NULL }, + }, + }, + }, + [23] = { + .pre = { &d, + &c, NULL, + &b, NULL, NULL, NULL, + &a, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, + .sub = { + [0] = { &a, + { &d, + &c, NULL, + &b, NULL, NULL, NULL }, + }, + [1] = { &b, + { &d, + &c, NULL, + &a, NULL, NULL, NULL }, + }, + [2] = { &c, + { &d, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + [3] = { &d, + { &c, + &b, NULL, + &a, NULL, NULL, NULL }, + }, + }, + }, +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_tree_common.c Wed Jul 25 14:19:27 2018 -0400 @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <jeffpc/types.h> +#include <jeffpc/bst.h> +#include <jeffpc/rbtree.h> + +#include "test.c" + +#if defined(TEST_TREE_BST) +#define TREE_TREE bst_tree +#define TREE_NODE bst_node +#define TREE_COOKIE bst_cookie +#define TREE_CREATE bst_create +#define TREE_DESTROY bst_destroy +#define TREE_ADD bst_add +#define TREE_REMOVE bst_remove +#define TREE_FOR_EACH bst_for_each +#define TREE_NUMNODES bst_numnodes +#define TREE_DESTROY_NODES bst_destroy_nodes +#elif defined(TEST_TREE_RB) +#define TREE_TREE rb_tree +#define TREE_NODE rb_node +#define TREE_COOKIE rb_cookie +#define TREE_CREATE rb_create +#define TREE_DESTROY rb_destroy +#define TREE_ADD rb_add +#define TREE_REMOVE rb_remove +#define TREE_FOR_EACH rb_for_each +#define TREE_NUMNODES rb_numnodes +#define TREE_DESTROY_NODES rb_destroy_nodes +#else +#error "Unspecified test type" +#endif + +struct node { + struct TREE_NODE node; + const char *name; + int v; +}; + +/* various balanced orderings */ +#define TEST3_BAC { &b, \ + &a, &c } +#define TEST3_BAD { &b, \ + &a, &d } +#define TEST3_CAD { &c, \ + &a, &d } +#define TEST3_CBD { &c, \ + &b, &d } + +#define TEST4_BACD { &b, \ + &a, &c, \ + NULL, NULL, NULL, &d } +#define TEST4_BADC { &b, \ + &a, &d, \ + NULL, NULL, &c, NULL } +#define TEST4_CADB { &c, \ + &a, &d, \ + NULL, &b, NULL, NULL } +#define TEST4_CBDA { &c, \ + &b, &d, \ + &a, NULL, NULL, NULL } + +static struct node a = { .name = "a", .v = 10 }; +static struct node b = { .name = "b", .v = 20 }; +static struct node c = { .name = "c", .v = 30 }; +static struct node d = { .name = "d", .v = 40 }; + +struct test2 { + const char *name; + struct node *pre[3]; + struct subtest { + struct node *remove; + struct node *post[1]; + } sub[2]; +}; + +struct test3 { + struct node *pre[7]; + struct { + struct node *remove; + struct node *nodes[3]; + } sub[3]; +}; + +struct test4 { + struct node *pre[15]; + struct { + struct node *remove; + struct node *nodes[7]; + } sub[4]; +}; + +static struct test2 checks2[2]; +static struct test3 checks3[6]; +static struct test4 checks4[24]; + +static int cmp(const void *va, const void *vb) +{ + const struct node *a = va; + const struct node *b = vb; + + if (a->v < b->v) + return -1; + if (a->v > b->v) + return 1; + return 0; +} + +static void init(struct TREE_TREE *tree, const char *testname, const char *nodename, + size_t numnodes, struct node **nodes, bool begin) +{ + size_t i; + + if (begin) + fprintf(stderr, "%zu nodes: %s - %s...", numnodes, testname, + nodename); + + TREE_CREATE(tree, cmp, sizeof(struct node), + offsetof(struct node, node)); + + /* insert the first set of pointers */ + for (i = 0; i < numnodes; i++) + TREE_ADD(tree, nodes[i]); + +} + +static size_t __destroy_destroy(struct TREE_TREE *tree) +{ + struct TREE_COOKIE cookie; + struct node *node; + size_t removed_nodes; + + fprintf(stderr, "destroy..."); + memset(&cookie, 0, sizeof(cookie)); + removed_nodes = 0; + + while ((node = TREE_DESTROY_NODES(tree, &cookie))) + removed_nodes++; + + return removed_nodes; +} + +static void destroy(struct TREE_TREE *tree, size_t expected_numnodes, + bool done) +{ + size_t remaining_nodes; + + remaining_nodes = TREE_NUMNODES(tree); + + VERIFY3U(remaining_nodes, ==, expected_numnodes); + + /* test destruction */ + remaining_nodes -= __destroy_destroy(tree); + + VERIFY0(remaining_nodes); + VERIFY0(TREE_NUMNODES(tree)); + + TREE_DESTROY(tree); + + if (done) + fprintf(stderr, "ok.\n"); +} + +static void __verify_nodes(struct tree_node *node, size_t numnodes, + struct node **exp, size_t level, + size_t leveloff) +{ + size_t idx = (1 << level) - 1 + leveloff; + + if ((idx >= numnodes) || !exp[idx]) { + VERIFY3P(node, ==, NULL); + return; + } + + if (node != &exp[idx]->node.node) { + panic("node (%p) != expected (%p) at index %zu", + node, &exp[idx]->node.node, idx); + } + + __verify_nodes(node->children[TREE_LEFT], numnodes, exp, + level + 1, leveloff * 2); + __verify_nodes(node->children[TREE_RIGHT], numnodes, exp, + level + 1, leveloff * 2 + 1); +} + +static void verify_nodes(struct TREE_TREE *tree, size_t numnodes, + struct node **exp) +{ + fprintf(stderr, "verify..."); + __verify_nodes(tree->tree.root, numnodes, exp, 0, 0); +} + +static void verify_iter(struct TREE_TREE *tree, size_t expected_numnodes) +{ + struct node *max_node; + struct node *node; + size_t found_nodes; + int max_so_far; + + fprintf(stderr, "iter..."); + VERIFY3U(TREE_NUMNODES(tree), ==, expected_numnodes); + + found_nodes = 0; + max_so_far = INT_MIN; + max_node = NULL; + TREE_FOR_EACH(tree, node) { + found_nodes++; + + if (max_so_far >= node->v) + panic("found node with value preceding previous " + "(current: %d @ %p, previous: %d @ %p)", + node->v, node, max_so_far, max_node); + + max_so_far = node->v; + max_node = node; + } + + VERIFY3U(TREE_NUMNODES(tree), ==, found_nodes); + VERIFY3U(TREE_NUMNODES(tree), ==, expected_numnodes); +} + +static void test_nodes(const char *testname, struct node *remove, + size_t numinsert, struct node **inserts, + size_t npre, struct node **pre, + size_t npost, struct node **post) +{ + struct TREE_TREE tree; + + init(&tree, testname, remove ? remove->name : "(none)", numinsert, inserts, true); +#if 0 + fprintf(stderr, " node dump:\n"); + for (size_t i = 0; i < numinsert; i++) + fprintf(stderr, " [%zu] = %p\n", i, inserts[i]); +#endif + verify_nodes(&tree, npre, pre); + verify_iter(&tree, numinsert); + destroy(&tree, numinsert, !remove); + + if (!remove) + return; + + init(&tree, NULL, NULL, numinsert, inserts, false); + verify_nodes(&tree, npre, pre); + verify_iter(&tree, numinsert); + fprintf(stderr, "remove..."); + TREE_REMOVE(&tree, remove); + verify_nodes(&tree, npost, post); + verify_iter(&tree, numinsert - 1); + destroy(&tree, numinsert - 1, true); +} + +static void test_empty(void) +{ + test_nodes("empty", NULL, + 0, NULL, 0, NULL, 0, NULL); +} + +static void test_one(void) +{ + struct node *inserts[1] = { &a }; + struct node *checks[1] = { &a }; + + test_nodes("root only", &a, + ARRAY_LEN(inserts), inserts, + ARRAY_LEN(checks), checks, + 0, NULL); +} + +static void test_two(bool asc) +{ + size_t i; + struct node *inserts[2][2] = { + [true] = { &a, &b }, + [false] = { &b, &a }, + }; + + for (i = 0; i < 2; i++) + test_nodes(checks2[asc].name, + checks2[asc].sub[i].remove, + ARRAY_LEN(inserts[asc]), inserts[asc], + ARRAY_LEN(checks2[asc].pre), checks2[asc].pre, + ARRAY_LEN(checks2[asc].sub[i].post), checks2[asc].sub[i].post); +} + +static void test_three(int perm) +{ + size_t i; + struct node *inserts[6][3] = { + [0] = { &a, &b, &c }, + [1] = { &a, &c, &b }, + [2] = { &b, &a, &c }, + [3] = { &b, &c, &a }, + [4] = { &c, &a, &b }, + [5] = { &c, &b, &a }, + }; + + for (i = 0; i < 3 ; i++) { + char name[64]; + + snprintf(name, sizeof(name), "permutation: %d", perm); + + test_nodes(name, checks3[perm].sub[i].remove, + ARRAY_LEN(inserts[perm]), inserts[perm], + ARRAY_LEN(checks3[perm].pre), checks3[perm].pre, + ARRAY_LEN(checks3[perm].sub[i].nodes), checks3[perm].sub[i].nodes); + } +} + +static void test_four(int perm) +{ + size_t i; + struct node *inserts[24][4] = { + [0] = { &a, &b, &c, &d }, + [1] = { &a, &c, &b, &d }, + [2] = { &b, &a, &c, &d }, + [3] = { &b, &c, &a, &d }, + [4] = { &c, &a, &b, &d }, + [5] = { &c, &b, &a, &d }, + + [6] = { &a, &b, &d, &c }, + [7] = { &a, &c, &d, &b }, + [8] = { &b, &a, &d, &c }, + [9] = { &b, &c, &d, &a }, + [10] = { &c, &a, &d, &b }, + [11] = { &c, &b, &d, &a }, + + [12] = { &a, &d, &b, &c }, + [13] = { &a, &d, &c, &b }, + [14] = { &b, &d, &a, &c }, + [15] = { &b, &d, &c, &a }, + [16] = { &c, &d, &a, &b }, + [17] = { &c, &d, &b, &a }, + + [18] = { &d, &a, &b, &c }, + [19] = { &d, &a, &c, &b }, + [20] = { &d, &b, &a, &c }, + [21] = { &d, &b, &c, &a }, + [22] = { &d, &c, &a, &b }, + [23] = { &d, &c, &b, &a }, + }; + + for (i = 0; i < 4 ; i++) { + char name[64]; + + snprintf(name, sizeof(name), "permutation: %d", perm); + + test_nodes(name, checks4[perm].sub[i].remove, + ARRAY_LEN(inserts[perm]), inserts[perm], + ARRAY_LEN(checks4[perm].pre), checks4[perm].pre, + ARRAY_LEN(checks4[perm].sub[i].nodes), checks4[perm].sub[i].nodes); + } +} + +static void test_insert(void) +{ + int i; + + test_empty(); + test_one(); + test_two(true); + test_two(false); + for (i = 0; i < 6; i++) + test_three(i); + for (i = 0; i < 24; i++) + test_four(i); +} + +void test(void) +{ + test_insert(); +}
--- a/tests/test_tree_destroy_nodes.c Wed Jul 25 12:16:02 2018 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include <jeffpc/types.h> -#include <jeffpc/bst.h> - -#include "test.c" - -struct node { - struct bst_node node; - int v; -}; - -static int cmp(const void *va, const void *vb) -{ - const struct node *a = va; - const struct node *b = vb; - - if (a->v < b->v) - return -1; - if (a->v > b->v) - return 1; - return 0; -} - -static void init(struct bst_tree *tree, const char *testname, - size_t numnodes) -{ - fprintf(stderr, "Testing %zu (%s)...", numnodes, testname); - - bst_create(tree, cmp, sizeof(struct node), - offsetof(struct node, node)); -} - -static void insert(struct bst_tree *tree, int val) -{ - struct node *node; - - node = malloc(sizeof(struct node)); - ASSERT3P(node, !=, NULL); - - node->v = val; - - bst_add(tree, node); -} - -static void __destroy_iter(struct bst_tree *tree, size_t expected_numnodes) -{ - struct node *node; - size_t found_nodes; - - VERIFY3U(bst_numnodes(tree), ==, expected_numnodes); - - found_nodes = 0; - bst_for_each(tree, node) - found_nodes++; - - VERIFY3U(bst_numnodes(tree), ==, found_nodes); - VERIFY3U(bst_numnodes(tree), ==, expected_numnodes); -} - -static size_t __destroy_destroy(struct bst_tree *tree) -{ - struct bst_cookie cookie; - struct bst_node *node; - size_t removed_nodes; - - memset(&cookie, 0, sizeof(cookie)); - removed_nodes = 0; - - while ((node = bst_destroy_nodes(tree, &cookie))) { - memset(node, 0xba, sizeof(struct node)); - removed_nodes++; - } - - return removed_nodes; -} - -static void destroy(struct bst_tree *tree, size_t expected_numnodes) -{ - size_t remaining_nodes; - - remaining_nodes = bst_numnodes(tree); - - VERIFY3U(remaining_nodes, ==, expected_numnodes); - - /* test iteration */ - __destroy_iter(tree, expected_numnodes); - - /* test destruction */ - remaining_nodes -= __destroy_destroy(tree); - - VERIFY0(remaining_nodes); - VERIFY0(bst_numnodes(tree)); - - bst_destroy(tree); - - fprintf(stderr, "ok.\n"); -} - -static void test_nodes(const char *testname, size_t numnodes, ...) -{ - struct bst_tree tree; - va_list ap; - size_t i; - - init(&tree, testname, numnodes); - va_start(ap, numnodes); - for (i = 0; i < numnodes; i++) - insert(&tree, va_arg(ap, int)); - va_end(ap); - destroy(&tree, numnodes); -} - -void test(void) -{ - test_nodes("empty", 0); - test_nodes("root", 1, 5); - test_nodes("left-lean", 2, 2, 1); - test_nodes("right-lean", 2, 1, 2); - test_nodes("balanced", 3, 2, 1, 3); - test_nodes("L-L", 3, 3, 2, 1); - test_nodes("L-R", 3, 3, 1, 2); - test_nodes("R-R", 3, 1, 2, 3); - test_nodes("R-L", 3, 1, 3, 2); - test_nodes("L-R-L-R", 5, 5, 1, 4, 2, 3); - test_nodes("R-L-R-L", 5, 1, 5, 2, 4, 3); - - test_nodes("complex A", 6, - /* row 1 */ 5, - /* row 2 */ 3, 6, - /* row 3 */ 1, 4, - /* row 4 */ 2); - test_nodes("complex B", 6, - /* row 1 */ 5, - /* row 2 */ 3, 6, - /* row 3 */ 2, 4, - /* row 4 */ 1); - test_nodes("complex C", 11, - /* row 1 */ 4, - /* row 2 */ 2, 8, - /* row 3 */ 1, 3, 6, 10, - /* row 4 */ 5, 7, 9, 11); - test_nodes("complex D", 10, - /* row 1 */ 4, - /* row 2 */ 2, 8, - /* row 3 */ 1, 3, 6, 9, - /* row 4 */ 5, 7, 10); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_tree_rb.c Wed Jul 25 14:19:27 2018 -0400 @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define TEST_TREE_RB + +#include "test_tree_common.c" + +static struct test2 checks2[2] = { + [true] = { + "ascending", + { &a, NULL, &b }, + { + { &a, { &b } }, + { &b, { &a } }, + }, + }, + [false] = { + "descending", + { &b, &a, NULL }, + { + { &a, { &b } }, + { &b, { &a } }, + }, + }, +}; + +/* + * Since all permutations create the same tree, we test removal only for one + * of them + */ +static struct test3 checks3[6] = { + [0] = { + .pre = { &b, &a, &c }, + .sub = { + [0] = { &a, { &b, NULL, &c }, }, + [1] = { &b, { &c, &a, NULL }, }, + [2] = { &c, { &b, &a, NULL }, }, + }, + }, + [1] = { + .pre = { &b, &a, &c }, + }, + [2] = { + .pre = { &b, &a, &c }, + }, + [3] = { + .pre = { &b, &a, &c }, + }, + [4] = { + .pre = { &b, &a, &c }, + }, + [5] = { + .pre = { &b, &a, &c }, + }, +}; + +/* + * Since all permutations create only four different trees, we test removal + * only once for each of them. + */ +static struct test4 checks4[24] = { + [0] = { + .pre = TEST4_BACD, + .sub = { + [0] = { &a, { &c, &b, &d }, }, + [1] = { &b, { &c, &a, &d }, }, + [2] = { &c, { &b, &a, &d }, }, + [3] = { &d, { &b, &a, &c }, }, + }, + }, + [1] = { + .pre = TEST4_BACD, + }, + [2] = { + .pre = TEST4_BACD, + }, + [3] = { + .pre = TEST4_BACD, + }, + [4] = { + .pre = TEST4_BACD, + }, + [5] = { + .pre = TEST4_BACD, + }, + + [6] = { + .pre = TEST4_BADC, + .sub = { + [0] = { &a, { &c, &b, &d }, }, + [1] = { &b, { &c, &a, &d }, }, + [2] = { &c, { &b, &a, &d }, }, + [3] = { &d, { &b, &a, &c }, }, + }, + }, + [7] = { + .pre = TEST4_CADB, + .sub = { + [0] = { &a, { &c, &b, &d }, }, + [1] = { &b, { &c, &a, &d }, }, + [2] = { &c, { &b, &a, &d }, }, + [3] = { &d, { &b, &a, &c }, }, + }, + }, + [8] = { + .pre = TEST4_BADC, + }, + [9] = { + .pre = TEST4_CBDA, + .sub = { + [0] = { &a, { &c, &b, &d }, }, + [1] = { &b, { &c, &a, &d }, }, + [2] = { &c, { &b, &a, &d }, }, + [3] = { &d, { &b, &a, &c }, }, + }, + }, + [10] = { + .pre = TEST4_CADB, + }, + [11] = { + .pre = TEST4_CBDA, + }, + + [12] = { + .pre = TEST4_BADC, + }, + [13] = { + .pre = TEST4_CADB, + }, + [14] = { + .pre = TEST4_BADC, + }, + [15] = { + .pre = TEST4_CBDA, + }, + [16] = { + .pre = TEST4_CADB, + }, + [17] = { + .pre = TEST4_CBDA, + }, + + [18] = { + .pre = TEST4_BADC, + }, + [19] = { + .pre = TEST4_CADB, + }, + [20] = { + .pre = TEST4_BADC, + }, + [21] = { + .pre = TEST4_CBDA, + }, + [22] = { + .pre = TEST4_CADB, + }, + [23] = { + .pre = TEST4_CBDA, + }, +};
--- a/tree.c Wed Jul 25 12:16:02 2018 -0400 +++ b/tree.c Wed Jul 25 14:19:27 2018 -0400 @@ -88,6 +88,174 @@ return NULL; } +static inline void __swap_nodes(struct tree_tree *tree, + struct tree_node *x, + struct tree_node *y, + const enum tree_dir left) +{ + const enum tree_dir right = 1 - left; + const bool root = tree->root == x; + enum tree_dir dir_to_orig_x; + bool tmp; + + ASSERT3P(x, !=, y); + ASSERT3P(y->children[left], ==, NULL); + + if (!root) + dir_to_orig_x = which_dir(x->parent, x); + + if (x->children[right] == y) { + /* + * A A + * \ \ + * \ \ + * x y + * / \ / \ + * / \ => / \ + * B y B x + * \ \ + * \ \ + * E E + */ + struct tree_node *A, *B, *E; + + A = x->parent; + B = x->children[left]; + E = y->children[right]; + + x->parent = y; + y->parent = A; + B->parent = y; + if (E) + E->parent = x; + + x->children[left] = NULL; + x->children[right] = E; + y->children[left] = B; + y->children[right] = x; + if (!root) + A->children[dir_to_orig_x] = y; + else + tree->root = y; + } else { + /* + * A A + * \ \ + * \ \ + * x y + * / \ / \ + * / \ => / \ + * B C B C + * / / + * . . + * . . + * / / + * D D + * / / + * / / + * y x + * \ \ + * \ \ + * E E + */ + struct tree_node *A, *B, *C, *D, *E; + + A = x->parent; + B = x->children[left]; + C = x->children[right]; + D = y->parent; + E = y->children[right]; + + x->parent = D; + y->parent = A; + B->parent = y; + C->parent = y; + if (E) + E->parent = x; + + x->children[left] = NULL; + x->children[right] = E; + y->children[left] = B; + y->children[right] = C; + if (!root) + A->children[dir_to_orig_x] = y; + else + tree->root = y; + D->children[left] = x; + } + + /* swap the colors */ + tmp = x->red; + x->red = y->red; + y->red = tmp; +} + +static inline void __promote_node_child(struct tree_tree *tree, + struct tree_node *parent, + struct tree_node *node, + struct tree_node *child) +{ + if (parent) + parent->children[which_dir(parent, node)] = child; + else + tree->root = child; + + if (child) + child->parent = parent; +} + +/* + * Remove @item from @tree. + * + * @parent_r: the parent node of @child_r (may be NULL) + * @child_r: the node that ultimately took @item's new place in the tree + * (may be NULL) + */ +void tree_remove(struct tree_tree *tree, void *item, + struct tree_node **parent_r, + struct tree_node **child_r) +{ + struct tree_node *parent; + struct tree_node *child; + struct tree_node *node; + + ASSERT3P(tree, !=, NULL); + ASSERT3P(item, !=, NULL); + + node = obj2node(tree, item); + + /* + * Two children: exchange it with a leaf-ish node greater than this + * node. + */ + if (node->children[TREE_LEFT] && node->children[TREE_RIGHT]) + __swap_nodes(tree, node, + firstlast(node->children[TREE_RIGHT], TREE_LEFT), + TREE_LEFT); + + /* now, we have zero or one child */ + ASSERT(!node->children[TREE_LEFT] || !node->children[TREE_RIGHT]); + + parent = node->parent; + if (node->children[TREE_LEFT]) + child = node->children[TREE_LEFT]; + else + child = node->children[TREE_RIGHT]; + + __promote_node_child(tree, parent, node, child); + + /* clear out the removed node */ + node->parent = NULL; + node->children[TREE_LEFT] = NULL; + node->children[TREE_RIGHT] = NULL; + + tree->num_nodes--; + + /* return various info */ + *parent_r = parent; + *child_r = child; +} + void *tree_first(struct tree_tree *tree) { return firstlast_obj(tree, TREE_LEFT);
--- a/tree_impl.h Wed Jul 25 12:16:02 2018 -0400 +++ b/tree_impl.h Wed Jul 25 14:19:27 2018 -0400 @@ -27,6 +27,9 @@ extern void *tree_insert_here(struct tree_tree *tree, void *newitem, struct tree_cookie *cookie); +extern void tree_remove(struct tree_tree *tree, void *item, + struct tree_node **parent_r, + struct tree_node **child_r); static inline void *node2obj(struct tree_tree *tree, struct tree_node *node) {