X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/81d77872a5683c1f0d5e27eb79bdbdcb7ac580f6..0965bee0865fd8ea129b2de62a3c50e09c59a184:/tree234.c diff --git a/tree234.c b/tree234.c index 6a570f70..7830aebb 100644 --- a/tree234.c +++ b/tree234.c @@ -6,10 +6,12 @@ #include #include +#include "puttymem.h" + #include "tree234.h" -#define mknew(typ) ( (typ *) malloc (sizeof (typ)) ) -#define sfree free +#define mknew(typ) ( (typ *) smalloc (sizeof (typ)) ) +/* #define sfree free */ #ifdef TEST #define LOG(x) (printf x) @@ -373,6 +375,7 @@ void del234(tree234 *t, void *e) { sub->kids[1] = sub->kids[0]; sub->elems[0] = n->elems[ki-1]; sub->kids[0] = sib->kids[lastelem+1]; + if (sub->kids[0]) sub->kids[0]->parent = sub; n->elems[ki-1] = sib->elems[lastelem]; sib->kids[lastelem+1] = NULL; sib->elems[lastelem] = NULL; @@ -392,6 +395,7 @@ void del234(tree234 *t, void *e) { int j; sub->elems[1] = n->elems[ki]; sub->kids[2] = sib->kids[0]; + if (sub->kids[2]) sub->kids[2]->parent = sub; n->elems[ki] = sib->elems[0]; sib->kids[0] = sib->kids[1]; for (j = 0; j < 2 && sib->elems[j+1]; j++) { @@ -434,8 +438,10 @@ void del234(tree234 *t, void *e) { sub->kids[2] = sub->kids[0]; sub->elems[1] = n->elems[ki]; sub->kids[1] = sib->kids[1]; + if (sub->kids[1]) sub->kids[1]->parent = sub; sub->elems[0] = sib->elems[0]; sub->kids[0] = sib->kids[0]; + if (sub->kids[0]) sub->kids[0]->parent = sub; sfree(sib); @@ -449,7 +455,7 @@ void del234(tree234 *t, void *e) { } n->kids[j] = NULL; if (j < 3) n->elems[j] = NULL; - LOG((" case 3b\n")); + LOG((" case 3b ki=%d\n", ki)); if (!n->elems[0]) { /* @@ -494,7 +500,7 @@ void del234(tree234 *t, void *e) { */ int i; LOG((" case 1\n")); - for (i = ei; i < 3 && n->elems[i+1]; i++) + for (i = ei; i < 2 && n->elems[i+1]; i++) n->elems[i] = n->elems[i+1]; n->elems[i] = NULL; return; /* finished! */ @@ -550,8 +556,10 @@ void del234(tree234 *t, void *e) { LOG((" case 2c\n")); a->elems[1] = n->elems[ei]; a->kids[2] = b->kids[0]; + if (a->kids[2]) a->kids[2]->parent = a; a->elems[2] = b->elems[0]; a->kids[3] = b->kids[1]; + if (a->kids[3]) a->kids[3]->parent = a; sfree(b); /* * That's built the big node in a, and destroyed b. Now @@ -563,6 +571,17 @@ void del234(tree234 *t, void *e) { } n->elems[j] = NULL; n->kids[j+1] = NULL; + /* + * It's possible, in this case, that we've just removed + * the only element in the root of the tree. If so, + * shift the root. + */ + if (n->elems[0] == NULL) { + LOG((" shifting root!\n")); + t->root = a; + a->parent = NULL; + sfree(n); + } /* * Now go round the deletion process again, with n * pointing at the new big node and e still the same. @@ -599,9 +618,9 @@ void *next234(enum234 *e) { return n->elems[0]; } - if (pos == 0 && n->elems[1]) { - e->posn = 1; - return n->elems[1]; + if (pos < 2 && n->elems[pos+1]) { + e->posn = pos+1; + return n->elems[e->posn]; } do { @@ -621,75 +640,307 @@ void *next234(enum234 *e) { #ifdef TEST -int pnode(node234 *n, int level) { - printf("%*s%p\n", level*4, "", n); - if (n->kids[0]) pnode(n->kids[0], level+1); - if (n->elems[0]) printf("%*s\"%s\"\n", level*4+4, "", n->elems[0]); - if (n->kids[1]) pnode(n->kids[1], level+1); - if (n->elems[1]) printf("%*s\"%s\"\n", level*4+4, "", n->elems[1]); - if (n->kids[2]) pnode(n->kids[2], level+1); - if (n->elems[2]) printf("%*s\"%s\"\n", level*4+4, "", n->elems[2]); - if (n->kids[3]) pnode(n->kids[3], level+1); +/* + * Test code for the 2-3-4 tree. This code maintains an alternative + * representation of the data in the tree, in an array (using the + * obvious and slow insert and delete functions). After each tree + * operation, the verify() function is called, which ensures all + * the tree properties are preserved (node->child->parent always + * equals node; number of kids == 0 or number of elements + 1; + * ordering property between elements of a node and elements of its + * children is preserved; tree has the same depth everywhere; every + * node has at least one element) and also ensures the list + * represented by the tree is the same list it should be. (This + * last check also verifies the ordering properties, because the + * `same list it should be' is by definition correctly ordered. It + * also ensures all nodes are distinct, because the enum functions + * would get caught in a loop if not.) + */ + +#include + +/* + * Error reporting function. + */ +void error(char *fmt, ...) { + va_list ap; + printf("ERROR: "); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf("\n"); } -int ptree(tree234 *t) { - if (t->root) - pnode(t->root, 0); - else - printf("empty tree\n"); + +/* The array representation of the data. */ +void **array; +int arraylen, arraysize; +cmpfn234 cmp; + +/* The tree representation of the same data. */ +tree234 *tree; + +typedef struct { + int treedepth; + int elemcount; +} chkctx; + +void chknode(chkctx *ctx, int level, node234 *node, + void *lowbound, void *highbound) { + int nkids, nelems; + int i; + + /* Count the non-NULL kids. */ + for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++); + /* Ensure no kids beyond the first NULL are non-NULL. */ + for (i = nkids; i < 4; i++) + if (node->kids[i]) { + error("node %p: nkids=%d but kids[%d] non-NULL", + node, nkids, i); + } + + /* Count the non-NULL elements. */ + for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++); + /* Ensure no elements beyond the first NULL are non-NULL. */ + for (i = nelems; i < 3; i++) + if (node->elems[i]) { + error("node %p: nelems=%d but elems[%d] non-NULL", + node, nelems, i); + } + + if (nkids == 0) { + /* + * If nkids==0, this is a leaf node; verify that the tree + * depth is the same everywhere. + */ + if (ctx->treedepth < 0) + ctx->treedepth = level; /* we didn't know the depth yet */ + else if (ctx->treedepth != level) + error("node %p: leaf at depth %d, previously seen depth %d", + node, level, ctx->treedepth); + } else { + /* + * If nkids != 0, then it should be nelems+1, unless nelems + * is 0 in which case nkids should also be 0 (and so we + * shouldn't be in this condition at all). + */ + int shouldkids = (nelems ? nelems+1 : 0); + if (nkids != shouldkids) { + error("node %p: %d elems should mean %d kids but has %d", + node, nelems, shouldkids, nkids); + } + } + + /* + * nelems should be at least 1. + */ + if (nelems == 0) { + error("node %p: no elems", node, nkids); + } + + /* + * Add nelems to the running element count of the whole tree + * (to ensure the enum234 routines see them all). + */ + ctx->elemcount += nelems; + + /* + * Check ordering property: all elements should be strictly > + * lowbound, strictly < highbound, and strictly < each other in + * sequence. (lowbound and highbound are NULL at edges of tree + * - both NULL at root node - and NULL is considered to be < + * everything and > everything. IYSWIM.) + */ + for (i = -1; i < nelems; i++) { + void *lower = (i == -1 ? lowbound : node->elems[i]); + void *higher = (i+1 == nelems ? highbound : node->elems[i+1]); + if (lower && higher && cmp(lower, higher) >= 0) { + error("node %p: kid comparison [%d=%s,%d=%s] failed", + node, i, lower, i+1, higher); + } + } + + /* + * Check parent pointers: all non-NULL kids should have a + * parent pointer coming back to this node. + */ + for (i = 0; i < nkids; i++) + if (node->kids[i]->parent != node) { + error("node %p kid %d: parent ptr is %p not %p", + node, i, node->kids[i]->parent, node); + } + + + /* + * Now (finally!) recurse into subtrees. + */ + for (i = 0; i < nkids; i++) { + void *lower = (i == 0 ? lowbound : node->elems[i-1]); + void *higher = (i >= nelems ? highbound : node->elems[i]); + chknode(ctx, level+1, node->kids[i], lower, higher); + } } -int cmp(void *av, void *bv) { - char *a = (char *)av; - char *b = (char *)bv; +void verify(void) { + chkctx ctx; + enum234 e; + int i; + void *p; + + ctx.treedepth = -1; /* depth unknown yet */ + ctx.elemcount = 0; /* no elements seen yet */ + /* + * Verify validity of tree properties. + */ + if (tree->root) + chknode(&ctx, 0, tree->root, NULL, NULL); + printf("tree depth: %d\n", ctx.treedepth); + /* + * Enumerate the tree and ensure it matches up to the array. + */ + for (i = 0, p = first234(tree, &e); + p; + i++, p = next234(&e)) { + if (i >= arraylen) + error("tree contains more than %d elements", arraylen); + if (array[i] != p) + error("enum at position %d: array says %s, tree says %s", + i, array[i], p); + } + if (i != ctx.elemcount) { + error("tree really contains %d elements, enum gave %d", + i, ctx.elemcount); + } + if (i < arraylen) { + error("enum gave only %d elements, array has %d", i, arraylen); + } +} + +void addtest(void *elem) { + int i, j; + void *retval, *realret; + + if (arraysize < arraylen+1) { + arraysize = arraylen+1+256; + array = (array == NULL ? smalloc(arraysize*sizeof(*array)) : + srealloc(array, arraysize*sizeof(*array))); + } + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + /* now i points to the first element >= elem */ + if (i < arraylen && !cmp(elem, array[i])) + retval = array[i]; /* expect that returned not elem */ + else { + retval = elem; /* expect elem returned (success) */ + for (j = arraylen; j > i; j--) + array[j] = array[j-1]; + array[i] = elem; /* add elem to array */ + arraylen++; + } + + realret = add234(tree, elem); + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + + verify(); +} + +void deltest(void *elem) { + int i; + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + /* now i points to the first element >= elem */ + if (i >= arraylen || cmp(elem, array[i]) != 0) + return; /* don't do it! */ + else { + while (i < arraylen-1) { + array[i] = array[i+1]; + i++; + } + arraylen--; /* delete elem from array */ + } + + del234(tree, elem); + + verify(); +} + +/* A sample data set and test utility. Designed for pseudo-randomness, + * and yet repeatability. */ + +/* + * This random number generator uses the `portable implementation' + * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits; + * change it if not. + */ +int randomnumber(unsigned *seed) { + *seed *= 1103515245; + *seed += 12345; + return ((*seed) / 65536) % 32768; +} + +int mycmp(void *av, void *bv) { + char const *a = (char const *)av; + char const *b = (char const *)bv; return strcmp(a, b); } +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +char *strings[] = { + "a", "ab", "absque", "coram", "de", + "palam", "clam", "cum", "ex", "e", + "sine", "tenus", "pro", "prae", + "banana", "carrot", "cabbage", "broccoli", "onion", "zebra", + "penguin", "blancmange", "pangolin", "whale", "hedgehog", + "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux", + "murfl", "spoo", "breen", "flarn", "octothorpe", + "snail", "tiger", "elephant", "octopus", "warthog", "armadillo", + "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin", + "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper", + "wand", "ring", "amulet" +}; + +#define NSTR lenof(strings) + int main(void) { - tree234 *t = newtree234(cmp); - - add234(t, "Richard"); - add234(t, "Of"); - add234(t, "York"); - add234(t, "Gave"); - add234(t, "Battle"); - add234(t, "In"); - add234(t, "Vain"); - add234(t, "Rabbits"); - add234(t, "On"); - add234(t, "Your"); - add234(t, "Garden"); - add234(t, "Bring"); - add234(t, "Invisible"); - add234(t, "Vegetables"); - - ptree(t); - del234(t, find234(t, "Richard", NULL)); - ptree(t); - del234(t, find234(t, "Of", NULL)); - ptree(t); - del234(t, find234(t, "York", NULL)); - ptree(t); - del234(t, find234(t, "Gave", NULL)); - ptree(t); - del234(t, find234(t, "Battle", NULL)); - ptree(t); - del234(t, find234(t, "In", NULL)); - ptree(t); - del234(t, find234(t, "Vain", NULL)); - ptree(t); - del234(t, find234(t, "Rabbits", NULL)); - ptree(t); - del234(t, find234(t, "On", NULL)); - ptree(t); - del234(t, find234(t, "Your", NULL)); - ptree(t); - del234(t, find234(t, "Garden", NULL)); - ptree(t); - del234(t, find234(t, "Bring", NULL)); - ptree(t); - del234(t, find234(t, "Invisible", NULL)); - ptree(t); - del234(t, find234(t, "Vegetables", NULL)); - ptree(t); + int in[NSTR]; + int i, j; + unsigned seed = 0; + + for (i = 0; i < NSTR; i++) in[i] = 0; + array = NULL; + arraylen = arraysize = 0; + tree = newtree234(mycmp); + cmp = mycmp; + + verify(); + for (i = 0; i < 10000; i++) { + j = randomnumber(&seed); + j %= NSTR; + printf("trial: %d\n", i); + if (in[j]) { + printf("deleting %s (%d)\n", strings[j], j); + deltest(strings[j]); + in[j] = 0; + } else { + printf("adding %s (%d)\n", strings[j], j); + addtest(strings[j]); + in[j] = 1; + } + } + + while (arraylen > 0) { + j = randomnumber(&seed); + j %= arraylen; + deltest(array[j]); + } + + return 0; } + #endif