Search notes:

Standard C library: search tree

The tsearch() and tfind() can be used to implement an associative array in C. The t stands for tree.
In the following example, the t…() functions are renamed to tq84_…() so as to be possible to be used with tq84-tsearch.c.
#ifdef TQ84_USE_GLIBC
 //
 // Defining _GNU_SOURCE exposes the GNU extension tdestroy(), which is not mentioned
 // or mandated by the Single Unix Specification.
 //
    #define _GNU_SOURCE    // in order to declare tdestroy

    #include <search.h>

 //
 // Because this program uses tq84_tsearch() for tsearch() etc, the
 // following defines route the function call to the glibc version.
 //

    #define tq84_tfind    tfind
    #define tq84_tsearch  tsearch
    #define tq84_tdelete  tdelete
    #define tq84_twalk    twalk
    #define tq84_tdestroy tdestroy
#else
    #include "tq84-tsearch.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

 //
 // This is the struct that defines the data
 // we want to insert into the tree.
 //
 // The key is used to identify a node, IT MUST
 // BE THE FIRST ELEMENT in the struct/data that
 // is inserted into the tree.
 //
typedef struct {
    int         key;
    const char* value;
} item;


 //
 // In order to find elements in the tree, we need to have a
 // function that is able to compare items.
 // The following call-back function is called by the t-search interface
 // whenever it needs to compare to inserted items (or keys).
 //
int compare_item_keys(const void *const item_left, const void *const item_right) {

 //
 // We cast both arguments to an (item*) so that we can access their key element.
 // by returning their difference, we're able to define an ordering among the
 // elements.
 //

    return ((item*)item_left )->key -
           ((item*)item_right)->key;
}


 //
 // Helper function for visited_node() to
 // show the value of the VISIT enum, see below.
 //
static const char* visit_str(VISIT order) {
    if (order == preorder ) { return "preorder" ; }
    if (order == postorder) { return "postorder"; }
    if (order == endorder ) { return "endorder" ; }
    if (order == leaf     ) { return "leaf"     ; }
    return "?";
}


 //
 // We also need a call-back function to be invoked for
 // every node when twalk() is called.
 //
static void visited_node(const void* node, VISIT order, int level) {
 //
 // Function to be called when twalk(…) is instructed to
 // walk the tree.
 //
 // level indicates how far the node is from thee tree's root.
 //
 // order indicates when the node is visited:
 //   Non leaf nodes:
 //     - preorder (the 1st visit): on the way down
 //     - postorder (the 2nd visit): coming up from the left subtree and going to the right subtree
 //     - endorder (the 3rd visit): on the way up
 //   Leaf nodes:
 //     Are Only visited once with order
 //     - leaf
 //
    int i;

 //
 // The argument node is really a pointer to a pointer
 // to the visited elem.
 // Thus, we cast it in such a pointer to pointer to elem
 // and dereference it once, resulting in a pointer to an
 // element:
 //
    item *item_;
    item_ = * ((item**) node);

 //
 // Print the indentation
 //
    for (i=0; i<level; i++) printf("  ");

 //
 // Print the item's data:
 //
    printf("Item %s (%d) was visited (%s)\n", item_->value, item_->key, visit_str(order));
}

 //
 // We need a variable on which to hinge the tree.
 // A pointer to void is sufficient. It's initialized
 // to NULL so as to indicate that the tree is empty.
 //
void *tree_root = NULL;

 //
 // The function that inserts an item into the tree.
 //
 //
static void insert_item(int k, const char* v) {

 //
 // Create the (new) item
 //
    item *new_item = malloc(sizeof(item));
    new_item->key   = k;
    new_item->value = v;

 //
 // tsearch searches for a node with a specific key.
 // If no such node was found, it inserts it and returns it.
 //
    item **item_in_tree = tq84_tsearch(new_item, &tree_root, compare_item_keys);

    if (*item_in_tree != new_item) {
     //
     // The address of the returned item is different from the
     // item we tried to insert. This means that we have already
     // inserted an item with the same key (which was returned
     // by tsearch)
     //
        printf("Already inserted: %d, overriting\n", k);

     //
     // Because the key was already inserted, we overwrite the
     // already inserted item's value with the new value
     //
      (*item_in_tree)->value = new_item->value;

     //
     // The new item is not needed anymore:
     //
        free(new_item);
    }
}

 //
 // the function that deletes an item from the tree.
 //
static void delete_item(int k) {

    item **item_in_tree;
    item  *item_;

 //
 // first, we try to to find the item that is to be deleted
 //
    if (item_in_tree = tq84_tfind(&k, &tree_root, compare_item_keys)) {

     //
     // The returned item_in_tree (which is acutally a pointer to a pointer to an item)
     // is not NULL. Thus, we have found an item.
     //

     //
     // In order to later free the item (not the node), we save it in
     // item_. It additionally makes it easier to access the data
     // in the struct, if still needed.
     //
     // NOTE WELL, we cannot delete the item here, because it is still
     // needed in the tdelete() call below (or more accuratly in the
     // comparison function that tdelete() calls to compare the items.
     //
        item_ = *item_in_tree;

        printf("Key to delete %d was found, deleting it.\n", k);

     //
     // Delete the node of the item in the tree.
     //
        tq84_tdelete(*item_in_tree, &tree_root, compare_item_keys);

     //
     // And finally free the item.
     //
        free(item_);
    }
    else {
        printf("Key to delete %d was not found\n", k);
    }
}

 //
 // A function that finds an item.
 //
static void find_item(int k) {

 //
 // We call tfind() to search for an item. If the item does
 // not exist, tfind() returns NULL. tsearch() would insert
 // a new item in such a case…
 //
    item **item_in_tree = tq84_tfind(&k, &tree_root, compare_item_keys);

    if (item_in_tree) {
        printf("Found %d, value is %s\n", k, (*item_in_tree)->value);
    }
    else {
        printf("Key %d was not found\n", k);
    }
}

 //
 // Used in conjunction with tdestroy()
 //
void free_item(void *node) {
     free( node );
}

int main(int argc, char **argv) {

    insert_item(4, "four" );
    insert_item(7, "seven");
    insert_item(2, "two"  );
    insert_item(5, "five" );

    insert_item(9, "nyne" ); // note the typo
    insert_item(1, "one"  );
    insert_item(8, "eight");
    insert_item(9, "nine" ); // correct nine
    insert_item(6, "six"  );

    find_item(5);
    find_item(4);
    find_item(3);
    find_item(7);

 //
 // Walk the tree and call visited_node() for nodes being walked.
 //
 // visited_node() is called three times for non-leaf nodes (order = preorder, postorder, endorder),
 // and once for leaf nodes (order=leaf)
 //
    tq84_twalk(tree_root, visited_node);

    delete_item(7);
    delete_item(4);
    delete_item(7);  // Cannot delete, was already deleted
    delete_item(3);  // Cannot delete, was not inserted at all

    delete_item(5);
    delete_item(2);
    delete_item(8);
    delete_item(9);
    delete_item(6);

    tq84_twalk(tree_root, visited_node);

    find_item(5);
    find_item(4);
    find_item(3);
    find_item(7);

 //
 // Delete everything that was left in the tree:
 //
    tq84_tdestroy(tree_root, free_item);

    return 0;
}
Github repository about-libc, path: /search/t/prog.c

TODO

The c subdirectory of a Strawberry Perl installation contains a tsearch directory that might have a search tree implementation.

See also

The Standard C Library

Links

https://www.prevanders.net/tsearch.html

Index