GTree: add an ability to iterate over a tree and a node-based API

The basic API that this commit adds allows in-order iterating over a GTree.

For this the following API were implemented or exported:
1) Returning the first or the last node in the tree,

2) Taking a pointer to a node in the tree and returning the previous or the
next in-order node,

3) Allowing to do a binary search for a particular key value and returning
the pointer to its node,

4) Returning the newly inserted or set node from both insert and replace
functions, so this node is immediately available and does not have to be
looked up,

5) Traversing the tree in-order providing a node pointer to the
caller-provided traversal function.

Most of the above functions were already present in the code, but they
returned the value that is stored at a particular node instead of the
pointer to the node itself.

So most of the code for these new API calls is shared with these existing
ones, just adapted to return the pointer to the node.

Additionally, the so called "lower bound" and "upper bound" operations
were implemented.

The first one returns the first element that is greater than or equal to
the searched key, while the second returns the first element that is
strictly greater than the searched key.

Signed-off-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com>
This commit is contained in:
Maciej S. Szmigiero 2020-08-04 21:31:36 +02:00 committed by Philip Withnall
parent 260e80f1df
commit 2e7931c760
3 changed files with 486 additions and 48 deletions

View File

@ -2982,21 +2982,36 @@ g_bytes_get_type
<TITLE>Balanced Binary Trees</TITLE>
<FILE>trees-binary</FILE>
GTree
GTreeNode
g_tree_new
g_tree_ref
g_tree_unref
g_tree_new_with_data
g_tree_new_full
g_tree_node_first
g_tree_node_last
g_tree_node_previous
g_tree_node_next
g_tree_insert_node
g_tree_insert
g_tree_replace_node
g_tree_replace
g_tree_node_key
g_tree_node_value
g_tree_nnodes
g_tree_height
g_tree_lookup_node
g_tree_lookup
g_tree_lookup_extended
g_tree_foreach_node
g_tree_foreach
g_tree_traverse
GTraverseFunc
GTraverseNodeFunc
g_tree_search_node
g_tree_search
g_tree_lower_bound
g_tree_upper_bound
g_tree_remove
g_tree_steal
g_tree_destroy

View File

@ -68,8 +68,6 @@
#define MAX_GTREE_HEIGHT 40
typedef struct _GTreeNode GTreeNode;
/**
* GTree:
*
@ -102,10 +100,10 @@ struct _GTreeNode
static GTreeNode* g_tree_node_new (gpointer key,
gpointer value);
static void g_tree_insert_internal (GTree *tree,
gpointer key,
gpointer value,
gboolean replace);
static GTreeNode *g_tree_insert_internal (GTree *tree,
gpointer key,
gpointer value,
gboolean replace);
static gboolean g_tree_remove_internal (GTree *tree,
gconstpointer key,
gboolean steal);
@ -121,9 +119,9 @@ static gint g_tree_node_in_order (GTreeNode *node,
static gint g_tree_node_post_order (GTreeNode *node,
GTraverseFunc traverse_func,
gpointer data);
static gpointer g_tree_node_search (GTreeNode *node,
GCompareFunc search_func,
gconstpointer data);
static GTreeNode *g_tree_node_search (GTreeNode *node,
GCompareFunc search_func,
gconstpointer data);
static GTreeNode* g_tree_node_rotate_left (GTreeNode *node);
static GTreeNode* g_tree_node_rotate_right (GTreeNode *node);
#ifdef G_TREE_DEBUG
@ -228,11 +226,24 @@ g_tree_new_full (GCompareDataFunc key_compare_func,
return tree;
}
static inline GTreeNode *
g_tree_first_node (GTree *tree)
/**
* g_tree_node_first:
* @tree: a #GTree
*
* Returns the first in-order node of the tree, or %NULL
* for an empty tree.
*
* Returns: (nullable) (transfer none): the first node in the tree
*
* Since: 2.68
*/
GTreeNode *
g_tree_node_first (GTree *tree)
{
GTreeNode *tmp;
g_return_val_if_fail (tree != NULL, NULL);
if (!tree->root)
return NULL;
@ -242,13 +253,55 @@ g_tree_first_node (GTree *tree)
tmp = tmp->left;
return tmp;
}
}
static inline GTreeNode *
/**
* g_tree_node_last:
* @tree: a #GTree
*
* Returns the last in-order node of the tree, or %NULL
* for an empty tree.
*
* Returns: (nullable) (transfer none): the last node in the tree
*
* Since: 2.68
*/
GTreeNode *
g_tree_node_last (GTree *tree)
{
GTreeNode *tmp;
g_return_val_if_fail (tree != NULL, NULL);
if (!tree->root)
return NULL;
tmp = tree->root;
while (tmp->right_child)
tmp = tmp->right;
return tmp;
}
/**
* g_tree_node_previous
* @node: a #GTree node
*
* Returns the previous in-order node of the tree, or %NULL
* if the passed node was already the first one.
*
* Returns: (nullable) (transfer none): the previous node in the tree
*
* Since: 2.68
*/
GTreeNode *
g_tree_node_previous (GTreeNode *node)
{
GTreeNode *tmp;
g_return_val_if_fail (node != NULL, NULL);
tmp = node->left;
if (node->left_child)
@ -258,11 +311,24 @@ g_tree_node_previous (GTreeNode *node)
return tmp;
}
static inline GTreeNode *
/**
* g_tree_node_next
* @node: a #GTree node
*
* Returns the next in-order node of the tree, or %NULL
* if the passed node was already the last one.
*
* Returns: (nullable) (transfer none): the next node in the tree
*
* Since: 2.68
*/
GTreeNode *
g_tree_node_next (GTreeNode *node)
{
GTreeNode *tmp;
g_return_val_if_fail (node != NULL, NULL);
tmp = node->right;
if (node->right_child)
@ -280,7 +346,7 @@ g_tree_remove_all (GTree *tree)
g_return_if_fail (tree != NULL);
node = g_tree_first_node (tree);
node = g_tree_node_first (tree);
while (node)
{
@ -378,7 +444,7 @@ g_tree_destroy (GTree *tree)
}
/**
* g_tree_insert:
* g_tree_insert_node:
* @tree: a #GTree
* @key: the key to insert
* @value: the value corresponding to the key
@ -396,28 +462,55 @@ g_tree_destroy (GTree *tree)
* The cost of maintaining a balanced tree while inserting new key/value
* result in a O(n log(n)) operation where most of the other operations
* are O(log(n)).
*
* Returns: (transfer none): the inserted (or set) node.
*
* Since: 2.68
*/
GTreeNode *
g_tree_insert_node (GTree *tree,
gpointer key,
gpointer value)
{
GTreeNode *node;
g_return_val_if_fail (tree != NULL, NULL);
node = g_tree_insert_internal (tree, key, value, FALSE);
#ifdef G_TREE_DEBUG
g_tree_node_check (tree->root);
#endif
return node;
}
/**
* g_tree_insert:
* @tree: a #GTree
* @key: the key to insert
* @value: the value corresponding to the key
*
* Inserts a key/value pair into a #GTree.
*
* Inserts a new key and value into a #GTree as g_tree_insert_node() does,
* only this function does not return the inserted or set node.
*/
void
g_tree_insert (GTree *tree,
gpointer key,
gpointer value)
{
g_return_if_fail (tree != NULL);
g_tree_insert_internal (tree, key, value, FALSE);
#ifdef G_TREE_DEBUG
g_tree_node_check (tree->root);
#endif
g_tree_insert_node (tree, key, value);
}
/**
* g_tree_replace:
* g_tree_replace_node:
* @tree: a #GTree
* @key: the key to insert
* @value: the value corresponding to the key
*
* Inserts a new key and value into a #GTree similar to g_tree_insert().
*
* Inserts a new key and value into a #GTree similar to g_tree_insert_node().
* The difference is that if the key already exists in the #GTree, it gets
* replaced by the new key. If you supplied a @value_destroy_func when
* creating the #GTree, the old value is freed using that function. If you
@ -426,39 +519,64 @@ g_tree_insert (GTree *tree,
*
* The tree is automatically 'balanced' as new key/value pairs are added,
* so that the distance from the root to every leaf is as small as possible.
*
* Returns: (transfer none): the inserted (or set) node.
*
* Since: 2.68
*/
GTreeNode *
g_tree_replace_node (GTree *tree,
gpointer key,
gpointer value)
{
GTreeNode *node;
g_return_val_if_fail (tree != NULL, NULL);
node = g_tree_insert_internal (tree, key, value, TRUE);
#ifdef G_TREE_DEBUG
g_tree_node_check (tree->root);
#endif
return node;
}
/**
* g_tree_replace:
* @tree: a #GTree
* @key: the key to insert
* @value: the value corresponding to the key
*
* Inserts a new key and value into a #GTree as g_tree_replace_node() does,
* only this function does not return the inserted or set node.
*/
void
g_tree_replace (GTree *tree,
gpointer key,
gpointer value)
{
g_return_if_fail (tree != NULL);
g_tree_insert_internal (tree, key, value, TRUE);
#ifdef G_TREE_DEBUG
g_tree_node_check (tree->root);
#endif
g_tree_replace_node (tree, key, value);
}
/* internal insert routine */
static void
static GTreeNode *
g_tree_insert_internal (GTree *tree,
gpointer key,
gpointer value,
gboolean replace)
{
GTreeNode *node;
GTreeNode *node, *retnode;
GTreeNode *path[MAX_GTREE_HEIGHT];
int idx;
g_return_if_fail (tree != NULL);
g_return_val_if_fail (tree != NULL, NULL);
if (!tree->root)
{
tree->root = g_tree_node_new (key, value);
tree->nnodes++;
return;
return tree->root;
}
idx = 0;
@ -490,7 +608,7 @@ g_tree_insert_internal (GTree *tree,
tree->key_destroy_func (key);
}
return;
return node;
}
else if (cmp < 0)
{
@ -511,6 +629,7 @@ g_tree_insert_internal (GTree *tree,
tree->nnodes++;
retnode = child;
break;
}
}
@ -533,6 +652,7 @@ g_tree_insert_internal (GTree *tree,
tree->nnodes++;
retnode = child;
break;
}
}
@ -569,6 +689,8 @@ g_tree_insert_internal (GTree *tree,
node = bparent;
}
return retnode;
}
/**
@ -844,6 +966,65 @@ g_tree_remove_internal (GTree *tree,
return TRUE;
}
/**
* g_tree_node_key:
* @node: a #GTree node
*
* Gets the key stored at a particular tree node.
*
* Returns: (nullable) (transfer none): the key at the node.
*
* Since: 2.68
*/
gpointer
g_tree_node_key (GTreeNode *node)
{
g_return_val_if_fail (node != NULL, NULL);
return node->key;
}
/**
* g_tree_node_value:
* @node: a #GTree node
*
* Gets the value stored at a particular tree node.
*
* Returns: (nullable) (transfer none): the value at the node.
*
* Since: 2.68
*/
gpointer
g_tree_node_value (GTreeNode *node)
{
g_return_val_if_fail (node != NULL, NULL);
return node->value;
}
/**
* g_tree_lookup_node:
* @tree: a #GTree
* @key: the key to look up
*
* Gets the tree node corresponding to the given key. Since a #GTree is
* automatically balanced as key/value pairs are added, key lookup
* is O(log n) (where n is the number of key/value pairs in the tree).
*
* Returns: (nullable) (transfer none): the tree node corresponding to
* the key, or %NULL if the key was not found
*
* Since: 2.68
*/
GTreeNode *
g_tree_lookup_node (GTree *tree,
gconstpointer key)
{
g_return_val_if_fail (tree != NULL, NULL);
return g_tree_find_node (tree, key);
}
/**
* g_tree_lookup:
* @tree: a #GTree
@ -862,10 +1043,8 @@ g_tree_lookup (GTree *tree,
{
GTreeNode *node;
g_return_val_if_fail (tree != NULL, NULL);
node = g_tree_lookup_node (tree, key);
node = g_tree_find_node (tree, key);
return node ? node->value : NULL;
}
@ -935,8 +1114,8 @@ g_tree_foreach (GTree *tree,
if (!tree->root)
return;
node = g_tree_first_node (tree);
node = g_tree_node_first (tree);
while (node)
{
if ((*func) (node->key, node->value, user_data))
@ -946,6 +1125,47 @@ g_tree_foreach (GTree *tree,
}
}
/**
* g_tree_foreach_node:
* @tree: a #GTree
* @func: the function to call for each node visited.
* If this function returns %TRUE, the traversal is stopped.
* @user_data: user data to pass to the function
*
* Calls the given function for each of the nodes in the #GTree.
* The function is passed the pointer to the particular node, and the given
* @data parameter. The tree traversal happens in-order.
*
* The tree may not be modified while iterating over it (you can't
* add/remove items). To remove all items matching a predicate, you need
* to add each item to a list in your #GTraverseFunc as you walk over
* the tree, then walk the list and remove each item.
*
* Since: 2.68
*/
void
g_tree_foreach_node (GTree *tree,
GTraverseNodeFunc func,
gpointer user_data)
{
GTreeNode *node;
g_return_if_fail (tree != NULL);
if (!tree->root)
return;
node = g_tree_node_first (tree);
while (node)
{
if ((*func) (node, user_data))
break;
node = g_tree_node_next (node);
}
}
/**
* g_tree_traverse:
* @tree: a #GTree
@ -1006,6 +1226,40 @@ g_tree_traverse (GTree *tree,
}
}
/**
* g_tree_search_node:
* @tree: a #GTree
* @search_func: a function used to search the #GTree
* @user_data: the data passed as the second argument to @search_func
*
* Searches a #GTree using @search_func.
*
* The @search_func is called with a pointer to the key of a key/value
* pair in the tree, and the passed in @user_data. If @search_func returns
* 0 for a key/value pair, then the corresponding node is returned as
* the result of g_tree_search(). If @search_func returns -1, searching
* will proceed among the key/value pairs that have a smaller key; if
* @search_func returns 1, searching will proceed among the key/value
* pairs that have a larger key.
*
* Returns: (nullable) (transfer none): the node corresponding to the
* found key, or %NULL if the key was not found
*
* Since: 2.68
*/
GTreeNode *
g_tree_search_node (GTree *tree,
GCompareFunc search_func,
gconstpointer user_data)
{
g_return_val_if_fail (tree != NULL, NULL);
if (!tree->root)
return NULL;
return g_tree_node_search (tree->root, search_func, user_data);
}
/**
* g_tree_search:
* @tree: a #GTree
@ -1030,12 +1284,119 @@ g_tree_search (GTree *tree,
GCompareFunc search_func,
gconstpointer user_data)
{
GTreeNode *node;
node = g_tree_search_node (tree, search_func, user_data);
return node ? node->value : NULL;
}
/**
* g_tree_lower_bound:
* @tree: a #GTree
* @key: the key to calculate the lower bound for
*
* Gets the lower bound node corresponding to the given key,
* or %NULL if the tree is empty or all the nodes in the tree
* have keys that are strictly lower than the searched key.
*
* The lower bound is the first node that has its key greater
* than or equal to the searched key.
*
* Returns: (nullable) (transfer none): the tree node corresponding to
* the lower bound, or %NULL if the tree is empty or has only
* keys strictly lower than the searched key.
*
* Since: 2.68
*/
GTreeNode *
g_tree_lower_bound (GTree *tree,
gconstpointer key)
{
GTreeNode *node, *result;
gint cmp;
g_return_val_if_fail (tree != NULL, NULL);
if (tree->root)
return g_tree_node_search (tree->root, search_func, user_data);
else
node = tree->root;
if (!node)
return NULL;
result = NULL;
while (1)
{
cmp = tree->key_compare (key, node->key, tree->key_compare_data);
if (cmp <= 0)
{
result = node;
if (!node->left_child)
return result;
node = node->left;
}
else
{
if (!node->right_child)
return result;
node = node->right;
}
}
}
/**
* g_tree_upper_bound:
* @tree: a #GTree
* @key: the key to calculate the upper bound for
*
* Gets the upper bound node corresponding to the given key,
* or %NULL if the tree is empty or all the nodes in the tree
* have keys that are lower than or equal to the searched key.
*
* The upper bound is the first node that has its key strictly greater
* than the searched key.
*
* Returns: (nullable) (transfer none): the tree node corresponding to the
* upper bound, or %NULL if the tree is empty or has only keys
* lower than or equal to the searched key.
*
* Since: 2.68
*/
GTreeNode *
g_tree_upper_bound (GTree *tree,
gconstpointer key)
{
GTreeNode *node, *result;
gint cmp;
g_return_val_if_fail (tree != NULL, NULL);
node = tree->root;
if (!node)
return NULL;
result = NULL;
while (1)
{
cmp = tree->key_compare (key, node->key, tree->key_compare_data);
if (cmp < 0)
{
result = node;
if (!node->left_child)
return result;
node = node->left;
}
else
{
if (!node->right_child)
return result;
node = node->right;
}
}
}
/**
@ -1212,7 +1573,7 @@ g_tree_node_post_order (GTreeNode *node,
return FALSE;
}
static gpointer
static GTreeNode *
g_tree_node_search (GTreeNode *node,
GCompareFunc search_func,
gconstpointer data)
@ -1226,7 +1587,7 @@ g_tree_node_search (GTreeNode *node,
{
dir = (* search_func) (node->key, data);
if (dir == 0)
return node->value;
return node;
else if (dir < 0)
{
if (!node->left_child)

View File

@ -37,10 +37,35 @@ G_BEGIN_DECLS
typedef struct _GTree GTree;
/**
* GTreeNode:
*
* An opaque type which identifies a specific node in a #GTree.
*
* Since: 2.68
*/
typedef struct _GTreeNode GTreeNode;
typedef gboolean (*GTraverseFunc) (gpointer key,
gpointer value,
gpointer data);
/**
* GTraverseNodeFunc:
* @node: a #GTreeNode
* @data: user data passed to g_tree_foreach_node()
*
* Specifies the type of function passed to g_tree_foreach_node(). It is
* passed each node, together with the @user_data parameter passed to
* g_tree_foreach_node(). If the function returns %TRUE, the traversal is
* stopped.
*
* Returns: %TRUE to stop the traversal
* Since: 2.68
*/
typedef gboolean (*GTraverseNodeFunc) (GTreeNode *node,
gpointer data);
/* Balanced binary trees
*/
GLIB_AVAILABLE_IN_ALL
@ -53,16 +78,32 @@ GTree* g_tree_new_full (GCompareDataFunc key_compare_func,
gpointer key_compare_data,
GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_node_first (GTree *tree);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_node_last (GTree *tree);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_node_previous (GTreeNode *node);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_node_next (GTreeNode *node);
GLIB_AVAILABLE_IN_ALL
GTree* g_tree_ref (GTree *tree);
GLIB_AVAILABLE_IN_ALL
void g_tree_unref (GTree *tree);
GLIB_AVAILABLE_IN_ALL
void g_tree_destroy (GTree *tree);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_insert_node (GTree *tree,
gpointer key,
gpointer value);
GLIB_AVAILABLE_IN_ALL
void g_tree_insert (GTree *tree,
gpointer key,
gpointer value);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_replace_node (GTree *tree,
gpointer key,
gpointer value);
GLIB_AVAILABLE_IN_ALL
void g_tree_replace (GTree *tree,
gpointer key,
@ -73,6 +114,13 @@ gboolean g_tree_remove (GTree *tree,
GLIB_AVAILABLE_IN_ALL
gboolean g_tree_steal (GTree *tree,
gconstpointer key);
GLIB_AVAILABLE_IN_2_68
gpointer g_tree_node_key (GTreeNode *node);
GLIB_AVAILABLE_IN_2_68
gpointer g_tree_node_value (GTreeNode *node);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_lookup_node (GTree *tree,
gconstpointer key);
GLIB_AVAILABLE_IN_ALL
gpointer g_tree_lookup (GTree *tree,
gconstpointer key);
@ -85,6 +133,10 @@ GLIB_AVAILABLE_IN_ALL
void g_tree_foreach (GTree *tree,
GTraverseFunc func,
gpointer user_data);
GLIB_AVAILABLE_IN_2_68
void g_tree_foreach_node (GTree *tree,
GTraverseNodeFunc func,
gpointer user_data);
GLIB_DEPRECATED
void g_tree_traverse (GTree *tree,
@ -92,10 +144,20 @@ void g_tree_traverse (GTree *tree,
GTraverseType traverse_type,
gpointer user_data);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_search_node (GTree *tree,
GCompareFunc search_func,
gconstpointer user_data);
GLIB_AVAILABLE_IN_ALL
gpointer g_tree_search (GTree *tree,
GCompareFunc search_func,
gconstpointer user_data);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_lower_bound (GTree *tree,
gconstpointer key);
GLIB_AVAILABLE_IN_2_68
GTreeNode *g_tree_upper_bound (GTree *tree,
gconstpointer key);
GLIB_AVAILABLE_IN_ALL
gint g_tree_height (GTree *tree);
GLIB_AVAILABLE_IN_ALL