/* * libmowgli: A collection of useful routines for programming. * dictionary.c: Dictionary-based information storage. * * Copyright (c) 2007 William Pitcock * Copyright (c) 2007 Jilles Tjoelker * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice is present in all copies. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "mowgli.h" static mowgli_heap_t *elem_heap = NULL; struct mowgli_dictionary_ { mowgli_dictionary_comparator_func_t compare_cb; mowgli_dictionary_elem_t *root, *head, *tail; unsigned int count; char *id; bool dirty; }; /* * mowgli_dictionary_create(mowgli_dictionary_comparator_func_t compare_cb) * * Dictionary object factory. * * Inputs: * - function to use for comparing two entries in the dtree * * Outputs: * - on success, a new dictionary object. * * Side Effects: * - if services runs out of memory and cannot allocate the object, * the program will abort. */ mowgli_dictionary_t * mowgli_dictionary_create(mowgli_dictionary_comparator_func_t compare_cb) { mowgli_dictionary_t *dtree = (mowgli_dictionary_t *) mowgli_alloc(sizeof(mowgli_dictionary_t)); dtree->compare_cb = compare_cb; if (!elem_heap) elem_heap = mowgli_heap_create(sizeof(mowgli_dictionary_elem_t), 1024, BH_NOW); return dtree; } /* * mowgli_dictionary_create_named(const char *name, * mowgli_dictionary_comparator_func_t compare_cb) * * Dictionary object factory. * * Inputs: * - dictionary name * - function to use for comparing two entries in the dtree * * Outputs: * - on success, a new dictionary object. * * Side Effects: * - if services runs out of memory and cannot allocate the object, * the program will abort. */ mowgli_dictionary_t * mowgli_dictionary_create_named(const char *name, mowgli_dictionary_comparator_func_t compare_cb) { mowgli_dictionary_t *dtree = (mowgli_dictionary_t *) mowgli_alloc(sizeof(mowgli_dictionary_t)); dtree->compare_cb = compare_cb; dtree->id = strdup(name); if (!elem_heap) elem_heap = mowgli_heap_create(sizeof(mowgli_dictionary_elem_t), 1024, BH_NOW); return dtree; } /* * mowgli_dictionary_set_comparator_func(mowgli_dictionary_t *dict, * mowgli_dictionary_comparator_func_t compare_cb) * * Resets the comparator function used by the dictionary code for * updating the DTree structure. * * Inputs: * - dictionary object * - new comparator function (passed as functor) * * Outputs: * - nothing * * Side Effects: * - the dictionary comparator function is reset. */ void mowgli_dictionary_set_comparator_func(mowgli_dictionary_t *dict, mowgli_dictionary_comparator_func_t compare_cb) { return_if_fail(dict != NULL); return_if_fail(compare_cb != NULL); dict->compare_cb = compare_cb; } /* * mowgli_dictionary_get_comparator_func(mowgli_dictionary_t *dict) * * Returns the current comparator function used by the dictionary. * * Inputs: * - dictionary object * * Outputs: * - comparator function (returned as functor) * * Side Effects: * - none */ mowgli_dictionary_comparator_func_t mowgli_dictionary_get_comparator_func(mowgli_dictionary_t *dict) { return_val_if_fail(dict != NULL, NULL); return dict->compare_cb; } /* * mowgli_dictionary_get_linear_index(mowgli_dictionary_t *dict, * const void *key) * * Gets a linear index number for key. * * Inputs: * - dictionary tree object * - pointer to data * * Outputs: * - position, from zero. * * Side Effects: * - rebuilds the linear index if the tree is marked as dirty. */ int mowgli_dictionary_get_linear_index(mowgli_dictionary_t *dict, const void *key) { mowgli_dictionary_elem_t *elem; return_val_if_fail(dict != NULL, 0); return_val_if_fail(key != NULL, 0); elem = mowgli_dictionary_find(dict, key); if (elem == NULL) return -1; if (!dict->dirty) { return elem->position; } else { mowgli_dictionary_elem_t *delem; int i; for (delem = dict->head, i = 0; delem != NULL; delem = delem->next, i++) delem->position = i; dict->dirty = false; } return elem->position; } /* * mowgli_dictionary_retune(mowgli_dictionary_t *dict, const void *key) * * Retunes the tree, self-optimizing for the element which belongs to key. * * Tuning the tree structure is a very complex operation. Unlike * 2-3-4 trees and BTree/BTree+ structures, this structure is a * constantly evolving algorithm. * * Instead of maintaining a balanced tree, we constantly adapt the * tree by nominating a new root nearby the most recently looked up * or added data. We are constantly retuning ourselves instead of * doing massive O(n) rebalance operations as seen in BTrees, * and the level of data stored in a tree is dynamic, instead of being * held to a restricted design like other trees. * * Moreover, we are different than a radix/patricia tree, because we * don't statically allocate positions. Radix trees have the advantage * of not requiring tuning or balancing operations while having the * disadvantage of requiring a large amount of memory to store * large trees. Our efficiency as far as speed goes is not as * fast as a radix tree; but is close to it. * * The retuning algorithm uses the comparison callback that is * passed in the initialization of the tree container. If the * comparator returns a value which is less than zero, we push the * losing node out of the way, causing it to later be reparented * with another node. The winning child of this comparison is always * the right-most node. * * Once we have reached the key which has been targeted, or have reached * a deadend, we nominate the nearest node as the new root of the tree. * If an exact match has been found, the new root becomes the node which * represents key. * * This results in a tree which can self-optimize for both critical * conditions: nodes which are distant and similar and trees which * have ordered lookups. * * Inputs: * - node to begin search from * * Outputs: * - none * * Side Effects: * - a new root node is nominated. */ void mowgli_dictionary_retune(mowgli_dictionary_t *dict, const void *key) { mowgli_dictionary_elem_t n, *tn, *left, *right, *node; ptrdiff_t ret; return_if_fail(dict != NULL); if (dict->root == NULL) return; /* * we initialize n with known values, since it's on stack * memory. otherwise the dict would become corrupted. * * n is used for temporary storage while the tree is retuned. * -nenolod */ n.left = n.right = NULL; left = right = &n; /* this for(;;) loop is the main workhorse of the rebalancing */ for (node = dict->root;;) { if ((ret = dict->compare_cb(key, node->key)) == 0) break; if (ret < 0) { if (node->left == NULL) break; if ((ret = dict->compare_cb(key, node->left->key)) < 0) { tn = node->left; node->left = tn->right; tn->right = node; node = tn; if (node->left == NULL) break; } right->left = node; right = node; node = node->left; } else { if (node->right == NULL) break; if ((ret = dict->compare_cb(key, node->right->key)) > 0) { tn = node->right; node->right = tn->left; tn->left = node; node = tn; if (node->right == NULL) break; } left->right = node; left = node; node = node->right; } } left->right = node->left; right->left = node->right; node->left = n.right; node->right = n.left; dict->root = node; } /* * mowgli_dictionary_link(mowgli_dictionary_t *dict, * mowgli_dictionary_elem_t *delem) * * Links a dictionary tree element to the dictionary. * * When we add new nodes to the tree, it becomes the * next nominated root. This is perhaps not a wise * optimization because of automatic retuning, but * it keeps the code simple. * * Inputs: * - dictionary tree * - dictionary tree element * * Outputs: * - nothing * * Side Effects: * - a node is linked to the dictionary tree */ void mowgli_dictionary_link(mowgli_dictionary_t *dict, mowgli_dictionary_elem_t *delem) { return_if_fail(dict != NULL); return_if_fail(delem != NULL); dict->dirty = true; dict->count++; if (dict->root == NULL) { delem->left = delem->right = NULL; delem->next = delem->prev = NULL; dict->head = dict->tail = dict->root = delem; } else { int ret; mowgli_dictionary_retune(dict, delem->key); if ((ret = dict->compare_cb(delem->key, dict->root->key)) < 0) { delem->left = dict->root->left; delem->right = dict->root; dict->root->left = NULL; if (dict->root->prev) dict->root->prev->next = delem; else dict->head = delem; delem->prev = dict->root->prev; delem->next = dict->root; dict->root->prev = delem; dict->root = delem; } else if (ret > 0) { delem->right = dict->root->right; delem->left = dict->root; dict->root->right = NULL; if (dict->root->next) dict->root->next->prev = delem; else dict->tail = delem; delem->next = dict->root->next; delem->prev = dict->root; dict->root->next = delem; dict->root = delem; } else { dict->root->key = delem->key; dict->root->data = delem->data; dict->count--; mowgli_heap_free(elem_heap, delem); } } } /* * mowgli_dictionary_unlink_root(mowgli_dictionary_t *dict) * * Unlinks the root dictionary tree element from the dictionary. * * Inputs: * - dictionary tree * * Outputs: * - nothing * * Side Effects: * - the root node is unlinked from the dictionary tree */ void mowgli_dictionary_unlink_root(mowgli_dictionary_t *dict) { mowgli_dictionary_elem_t *delem, *nextnode, *parentofnext; dict->dirty = true; delem = dict->root; if (delem == NULL) return; if (dict->root->left == NULL) { dict->root = dict->root->right; } else if (dict->root->right == NULL) { dict->root = dict->root->left; } else { /* Make the node with the next highest key the new root. * This node has a NULL left pointer. */ nextnode = delem->next; soft_assert(nextnode->left == NULL); if (nextnode == delem->right) { dict->root = nextnode; dict->root->left = delem->left; } else { parentofnext = delem->right; while (parentofnext->left != NULL && parentofnext->left != nextnode) { parentofnext = parentofnext->left; } soft_assert(parentofnext->left == nextnode); parentofnext->left = nextnode->right; dict->root = nextnode; dict->root->left = delem->left; dict->root->right = delem->right; } } /* linked list */ if (delem->prev != NULL) delem->prev->next = delem->next; if (dict->head == delem) dict->head = delem->next; if (delem->next) delem->next->prev = delem->prev; if (dict->tail == delem) dict->tail = delem->prev; dict->count--; } /* * mowgli_dictionary_destroy(mowgli_dictionary_t *dtree, * void (*destroy_cb)(dictionary_elem_t *delem, void *privdata), * void *privdata); * * Recursively destroys all nodes in a dictionary tree. * * Inputs: * - dictionary tree object * - optional iteration callback * - optional opaque/private data to pass to callback * * Outputs: * - nothing * * Side Effects: * - on success, a dtree and optionally it's children are destroyed. * * Notes: * - if this is called without a callback, the objects bound to the * DTree will not be destroyed. */ void mowgli_dictionary_destroy(mowgli_dictionary_t *dtree, void (*destroy_cb)(mowgli_dictionary_elem_t *delem, void *privdata), void *privdata) { mowgli_dictionary_elem_t *n, *tn; return_if_fail(dtree != NULL); MOWGLI_LIST_FOREACH_SAFE(n, tn, dtree->head) { if (destroy_cb != NULL) (*destroy_cb)(n, privdata); mowgli_heap_free(elem_heap, n); } mowgli_free(dtree); } /* * mowgli_dictionary_foreach(mowgli_dictionary_t *dtree, * void (*destroy_cb)(dictionary_elem_t *delem, void *privdata), * void *privdata); * * Iterates over all entries in a DTree. * * Inputs: * - dictionary tree object * - optional iteration callback * - optional opaque/private data to pass to callback * * Outputs: * - nothing * * Side Effects: * - on success, a dtree is iterated */ void mowgli_dictionary_foreach(mowgli_dictionary_t *dtree, int (*foreach_cb)(mowgli_dictionary_elem_t *delem, void *privdata), void *privdata) { mowgli_dictionary_elem_t *n, *tn; return_if_fail(dtree != NULL); MOWGLI_LIST_FOREACH_SAFE(n, tn, dtree->head) { /* delem_t is a subclass of node_t. */ mowgli_dictionary_elem_t *delem = (mowgli_dictionary_elem_t *) n; if (foreach_cb != NULL) (*foreach_cb)(delem, privdata); } } /* * mowgli_dictionary_search(mowgli_dictionary_t *dtree, * void (*destroy_cb)(mowgli_dictionary_elem_t *delem, void *privdata), * void *privdata); * * Searches all entries in a DTree using a custom callback. * * Inputs: * - dictionary tree object * - optional iteration callback * - optional opaque/private data to pass to callback * * Outputs: * - on success, the requested object * - on failure, NULL. * * Side Effects: * - a dtree is iterated until the requested conditions are met */ void * mowgli_dictionary_search(mowgli_dictionary_t *dtree, void *(*foreach_cb)(mowgli_dictionary_elem_t * delem, void *privdata), void *privdata) { mowgli_dictionary_elem_t *n, *tn; void *ret = NULL; return_val_if_fail(dtree != NULL, NULL); MOWGLI_LIST_FOREACH_SAFE(n, tn, dtree->head) { /* delem_t is a subclass of node_t. */ mowgli_dictionary_elem_t *delem = (mowgli_dictionary_elem_t *) n; if (foreach_cb != NULL) ret = (*foreach_cb)(delem, privdata); if (ret) break; } return ret; } /* * mowgli_dictionary_foreach_start(mowgli_dictionary_t *dtree, * mowgli_dictionary_iteration_state_t *state); * * Initializes a static DTree iterator. * * Inputs: * - dictionary tree object * - static DTree iterator * * Outputs: * - nothing * * Side Effects: * - the static iterator, &state, is initialized. */ void mowgli_dictionary_foreach_start(mowgli_dictionary_t *dtree, mowgli_dictionary_iteration_state_t *state) { return_if_fail(dtree != NULL); return_if_fail(state != NULL); state->cur = NULL; state->next = NULL; /* find first item */ state->cur = dtree->head; if (state->cur == NULL) return; /* make state->cur point to first item and state->next point to * second item */ state->next = state->cur; mowgli_dictionary_foreach_next(dtree, state); } /* * mowgli_dictionary_foreach_cur(mowgli_dictionary_t *dtree, * mowgli_dictionary_iteration_state_t *state); * * Returns the data from the current node being iterated by the * static iterator. * * Inputs: * - dictionary tree object * - static DTree iterator * * Outputs: * - reference to data in the current dtree node being iterated * * Side Effects: * - none */ void * mowgli_dictionary_foreach_cur(mowgli_dictionary_t *dtree, mowgli_dictionary_iteration_state_t *state) { return_val_if_fail(dtree != NULL, NULL); return_val_if_fail(state != NULL, NULL); return state->cur != NULL ? state->cur->data : NULL; } /* * mowgli_dictionary_foreach_next(mowgli_dictionary_t *dtree, * mowgli_dictionary_iteration_state_t *state); * * Advances a static DTree iterator. * * Inputs: * - dictionary tree object * - static DTree iterator * * Outputs: * - nothing * * Side Effects: * - the static iterator, &state, is advanced to a new DTree node. */ void mowgli_dictionary_foreach_next(mowgli_dictionary_t *dtree, mowgli_dictionary_iteration_state_t *state) { return_if_fail(dtree != NULL); return_if_fail(state != NULL); if (state->cur == NULL) { mowgli_log("mowgli_dictionary_foreach_next(): called again after iteration finished on dtree<%p>", (void *) dtree); return; } state->cur = state->next; if (state->next == NULL) return; state->next = state->next->next; } /* * mowgli_dictionary_find(mowgli_dictionary_t *dtree, const void *key) * * Looks up a DTree node by name. * * Inputs: * - dictionary tree object * - name of node to lookup * * Outputs: * - on success, the dtree node requested * - on failure, NULL * * Side Effects: * - none */ mowgli_dictionary_elem_t * mowgli_dictionary_find(mowgli_dictionary_t *dict, const void *key) { return_val_if_fail(dict != NULL, NULL); return_val_if_fail(key != NULL, NULL); /* retune for key, key will be the tree's root if it's available */ mowgli_dictionary_retune(dict, key); if (dict->root && !dict->compare_cb(key, dict->root->key)) return dict->root; return NULL; } /* * mowgli_dictionary_add(mowgli_dictionary_t *dtree, const void *key, void *data) * * Creates a new DTree node and binds data to it. * * Inputs: * - dictionary tree object * - name for new DTree node * - data to bind to the new DTree node * * Outputs: * - on success, a new DTree node * - on failure, NULL * * Side Effects: * - data is inserted into the DTree. */ mowgli_dictionary_elem_t * mowgli_dictionary_add(mowgli_dictionary_t *dict, const void *key, void *data) { mowgli_dictionary_elem_t *delem; return_val_if_fail(dict != NULL, NULL); return_val_if_fail(key != NULL, NULL); return_val_if_fail(data != NULL, NULL); return_val_if_fail(mowgli_dictionary_find(dict, key) == NULL, NULL); delem = mowgli_heap_alloc(elem_heap); delem->key = key; delem->data = data; if (delem->key == NULL) { mowgli_log("major WTF: delem->key<%p> is NULL, not adding node.", (void *) key); mowgli_heap_free(elem_heap, delem); return NULL; } mowgli_dictionary_link(dict, delem); return delem; } /* * mowgli_dictionary_delete(mowgli_dictionary_t *dtree, const void *key) * * Deletes data from a dictionary tree. * * Inputs: * - dictionary tree object * - name of DTree node to delete * * Outputs: * - on success, the remaining data that needs to be mowgli_freed * - on failure, NULL * * Side Effects: * - data is removed from the DTree. * * Notes: * - the returned data needs to be mowgli_freed/released manually! */ void * mowgli_dictionary_delete(mowgli_dictionary_t *dtree, const void *key) { mowgli_dictionary_elem_t *delem = mowgli_dictionary_find(dtree, key); void *data; if (delem == NULL) return NULL; data = delem->data; mowgli_dictionary_unlink_root(dtree); mowgli_heap_free(elem_heap, delem); return data; } /* * mowgli_dictionary_retrieve(mowgli_dictionary_t *dtree, const void *key) * * Retrieves data from a dictionary. * * Inputs: * - dictionary tree object * - name of node to lookup * * Outputs: * - on success, the data bound to the DTree node. * - on failure, NULL * * Side Effects: * - none */ void * mowgli_dictionary_retrieve(mowgli_dictionary_t *dtree, const void *key) { mowgli_dictionary_elem_t *delem = mowgli_dictionary_find(dtree, key); if (delem != NULL) return delem->data; return NULL; } /* * mowgli_dictionary_size(mowgli_dictionary_t *dict) * * Returns the size of a dictionary. * * Inputs: * - dictionary tree object * * Outputs: * - size of dictionary * * Side Effects: * - none */ unsigned int mowgli_dictionary_size(mowgli_dictionary_t *dict) { return_val_if_fail(dict != NULL, 0); return dict->count; } /* returns the sum of the depths of the subtree rooted in delem at depth depth */ static int stats_recurse(mowgli_dictionary_elem_t *delem, int depth, int *pmaxdepth) { int result; if (depth > *pmaxdepth) *pmaxdepth = depth; result = depth; if (delem->left) result += stats_recurse(delem->left, depth + 1, pmaxdepth); if (delem->right) result += stats_recurse(delem->right, depth + 1, pmaxdepth); return result; } /* * mowgli_dictionary_stats(mowgli_dictionary_t *dict, void (*cb)(const char *line, void *privdata), void *privdata) * * Returns the size of a dictionary. * * Inputs: * - dictionary tree object * - callback * - data for callback * * Outputs: * - none * * Side Effects: * - callback called with stats text */ void mowgli_dictionary_stats(mowgli_dictionary_t *dict, void (*cb)(const char *line, void *privdata), void *privdata) { char str[256]; int sum, maxdepth; return_if_fail(dict != NULL); if (dict->id != NULL) snprintf(str, sizeof str, "Dictionary stats for %s (%d)", dict->id, dict->count); else snprintf(str, sizeof str, "Dictionary stats for <%p> (%d)", (void *) dict, dict->count); cb(str, privdata); maxdepth = 0; if (dict->root != NULL) { sum = stats_recurse(dict->root, 0, &maxdepth); snprintf(str, sizeof str, "Depth sum %d Avg depth %d Max depth %d", sum, sum / dict->count, maxdepth); } else { snprintf(str, sizeof str, "Depth sum 0 Avg depth 0 Max depth 0"); } cb(str, privdata); return; }