gambas-source-code/main/lib/data/trie.c
Tobias Boege 4333eb8046 [GB.DATA]
* BUG: Trie: Fix a memory leak when an object is replaced



git-svn-id: svn://localhost/gambas/trunk@6701 867c0c6c-44f3-4631-809d-bfa615b0a4ec
2014-12-01 22:39:28 +00:00

756 lines
20 KiB
C

/*
* trie.c
*
* Copyright (C) 2014 Tobias Boege <tobias@gambas-buch.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdlib.h>
#include <string.h>
#include "trie.h"
#include "c_trie.h"
/**
* __key_index() - Return a unique number for the character
* @c: char
*/
static inline int __key_index(char c)
{
return (int) (unsigned char) c;
}
static inline int popcnt(uint64_t word)
{
int n;
for (n = 0; word; n++)
word &= word - 1;
return n;
}
#define MASK_SIZE \
(__CHAR_BIT__ * sizeof(((struct trie *) 0)->mask[0]))
#define INDEX(i) (i / MASK_SIZE)
#define OFFSET(i) (i % MASK_SIZE)
/**
* __key_to_array_index() - Return array index over a node's ->children
* corresponding to a key character
* @node: struct trie
* @c: the character
*/
static inline int __key_to_array_index(const struct trie *node, char c)
{
int i = __key_index(c), j, n;
for (j = n = 0; i >= MASK_SIZE; j++, i -= MASK_SIZE)
n += popcnt(node->mask[j]);
n += popcnt(node->mask[j] & ((1ULL << i) - 1));
return n;
}
static inline void __set_bit(uint64_t mask[4], int i)
{
mask[INDEX(i)] |= 1ULL << (OFFSET(i));
}
static inline void set_bit(struct trie *node, int i)
{
__set_bit(node->mask, i);
}
static inline void clear_bit(struct trie *node, int i)
{
node->mask[INDEX(i)] &= ~(1ULL << (OFFSET(i)));
}
static inline int test_bit(const struct trie *node, int i)
{
return !!(node->mask[INDEX(i)] & (1ULL << (OFFSET(i))));
}
/**
* get_continuation() - Return the node continuing the key of another node
* with a given character
* @node: struct trie
* @c: the character
*
* If you have a trie like
*
* 0
* |
* te
* |
* +--+--+
* | |
* st rm
*
* and search for the key "term", this function comes in handy at the node
* "te". You will call get_continuation(te_node, 'r') which yields rm_node.
*
* If no such continuation exists, NULL is returned.
*/
static inline struct trie *get_continuation(const struct trie *node, char c)
{
int i = __key_index(c);
int j = __key_to_array_index(node, c);
if (!test_bit(node, i))
return NULL;
return node->children[j];
}
struct __trie_find_res {
struct trie *node, *parent;
int i, j;
};
/**
* __trie_find() - Get the node containing a key
* @trie: struct trie
* @key: the key
* @len: length
*
* This function returns the node in which `key' ends which may NOT be the
* node which has exactly the key `key'. It returns NULL, if no such node
* was found.
*/
static struct __trie_find_res __trie_find(const struct trie *trie,
const char *key, size_t len)
{
struct trie *node = (struct trie *) trie, *parent = NULL;
int i = 0, j = 0;
while (node) {
i = 0;
while (i < node->len && j < len && node->key[i] == key[j]) {
i++;
j++;
}
/*
* Four cases:
* 1) the `key' and `node' were entirely consumed: perfect
* match. Get out.
* 2) only `key' consumed: we're done as the key lies
* within the node.
* 3) only `node' was consumed: recurse to its children.
* 4) if neither of the above, node and key deverged here,
* so break the loop since this is as close as we can
* get.
*/
if (j == len) {
break;
} else if (i == node->len) {
parent = node;
node = get_continuation(node, key[j]);
} else {
break;
}
}
return (struct __trie_find_res) {node, parent, i, j};
}
/**
* __is_exact() - Return whether a found node matches a key exactly
* @res: struct __trie_find_res
* @len: length
*/
static inline int __is_exact(struct __trie_find_res *res, size_t len)
{
return res->i == res->node->len && res->j == len;
}
/**
* __trie_find_exact() - Get the node with ends in a key
* @trie: struct trie
* @key: the key
* @len: length
*
* Unlike __trie_find(), this function returns the node which has the key
* `key' or NULL if none. Note, however, that this is not a guarantee that
* the node contains a non-NULL ->value.
*/
static struct trie *__trie_find_exact(const struct trie *trie,
const char *key, size_t len)
{
struct __trie_find_res res = __trie_find(trie, key, len);
return (res.node && __is_exact(&res, len)) ? res.node : NULL;
}
/**
* __new_node() - Allocate a trie node
* @key: part of the key
* @len: length of `key'
* @value: payload
*
* If `len' equals zero, the length is obtained from `key' via strlen(). For
* the single case where a zero length is correct, this doesn't do much
* harm - as opposed to the strangeness of comparing a size_t to -1 or some
* other clearly invalid value.
*/
static struct trie *new_node(const char *key, size_t len, void *value)
{
struct trie *trie;
/*if (!len)
len = strlen(key);*/
GB.Alloc((void **) &trie, sizeof(*trie) + len);
memset(trie->mask, 0, sizeof(trie->mask));
trie->children = NULL;
trie->nchildren = 0;
trie->value = value;
trie->len = len;
memcpy(trie->key, key, len);
return trie;
}
/**
* new_trie() - Allocate a new trie
*/
struct trie *new_trie(void)
{
return new_node("", 0, NULL);
}
/**
* destroy_node() - Deallocate a single node
* @node: struct trie
* @dtor: value destructor
*/
static void destroy_node(struct trie *node, void (*dtor)(void *))
{
GB.Free((void **) &node->children);
if (node->value && dtor)
dtor(node->value);
GB.Free((void **) &node);
}
/**
* destroy_trie() - Deallocate an entire trie
* @trie: struct trie
* @dtor: value destructor
*/
void destroy_trie(struct trie *trie, void (*dtor)(void *))
{
int i;
for (i = 0; i < trie->nchildren; i++)
destroy_trie(trie->children[i], dtor);
destroy_node(trie, dtor);
}
/**
* clear_trie() - Remove all but the root
* @trie: struct trie
* @dtor: value destructor
*/
void clear_trie(struct trie *trie, void (*dtor)(void *))
{
int i;
for (i = 0; i < trie->nchildren; i++)
destroy_trie(trie->children[i], dtor);
memset(trie->mask, 0, sizeof(trie->mask));
GB.Free((void **) &trie->children);
trie->children = NULL;
trie->nchildren = 0;
if (trie->value)
dtor(trie->value);
trie->value = NULL;
}
/**
* __sort_two_children() - Sort (at most) two children into a children array
* @array: array with enough space for the element(s)
* @mask: buffer
* @child1: struct trie
* @child2: struct trie, may be NULL
*
* This function writes the apropriate mask for the array into the `mask'
* argument.
*
* The `child2' can be NULL in which case it is ignored and not assigned to
* the array.
*/
static inline void __sort_two_children(const struct trie *array[2],
uint64_t mask[4],
const struct trie *child1,
const struct trie *child2)
{
int i, j;
i = __key_index(*child1->key);
j = child2 ? __key_index(*child2->key) : 0; /* just to initialise */
if (!child2 || i < j) {
array[0] = child1;
if (child2)
array[1] = child2;
} else {
array[0] = child2;
array[1] = child1;
}
__set_bit(mask, i);
if (child2)
__set_bit(mask, j);
}
/**
* __trie_insert_split() - Split a node to insert a new key
* @res: struct __trie_find_res
* @key: the key
* @len: length
* @value: the value
*/
static void __trie_insert_split(struct __trie_find_res *res, const char *key,
size_t len, void *value)
{
struct trie *node = res->node, *bottom, *branch = NULL;
struct trie **topchildren;
/*
* If key[res->j] == '\0', the key lies within `node' and will be in
* the "top" node already, so we save the `branch'.
*/
int have_branch = !!key[res->j];
/*
* - `bottom' will contain the bottom part of the split node;
* - `branch' will be the new node associated with the wanted key
* (if it is not within the `node')
* - `topchildren' is the new ->children array of the "top" half of
* the split node - which will consist of `bottom' and `branch'.
*/
bottom = new_node(&node->key[res->i], node->len - res->i,
node->value);
if (have_branch) {
branch = new_node(&key[res->j], len - res->j, value);
GB.Alloc((void **) &topchildren, 2 * sizeof(*topchildren));
} else {
GB.Alloc((void **) &topchildren, sizeof(*topchildren));
}
/* While doing the Alloc() stuff, we can already Realloc() the
* "top" node here... */
GB.Realloc((void **) &node, sizeof(*node) + res->j);
/* Link the split node into the trie again */
int i = __key_to_array_index(res->parent, *node->key);
res->parent->children[i] = node;
/*
* new_node() set `bottom' already up quite well. However, we need
* to tweak: ->mask, ->children and ->nchildren.
*
* After we have copied them from the "top" node, we can set the
* members there correctly to: ->mask, ->children, ->nchildren,
* ->value and ->len need tweaking while ->key was cut properly by
* Realloc().
*/
memcpy(bottom->mask, node->mask, sizeof(bottom->mask));
bottom->children = node->children;
bottom->nchildren = node->nchildren;
/*
* __sort_two_children() is aware that `branch' may be NULL.
*/
memset(node->mask, 0, sizeof(node->mask));
__sort_two_children((const struct trie **) topchildren,
node->mask, bottom, branch);
node->children = topchildren;
node->nchildren = have_branch ? 2 : 1;
node->value = NULL;
node->len = res->i;
/*
* The new `branch' has everything right if it exists as it has no
* children which need extra care. If it wasn't created, we need to
* assign the `value' to the "top" node now.
*/
if (!have_branch)
node->value = value;
}
/**
* __trie_insert_child() - Extend an already existing key to a new one
* @res: struct __trie_find_res
* @key: the key
* @len: length
* @value: the value
*/
static void __trie_insert_child(struct __trie_find_res *res, const char *key,
size_t len, void *value)
{
struct trie *node = res->parent, *child, **children;
int i, j, k;
/*
* Here, we CAN'T have the case that `node' has no children and no
* value so that we could concatenate the keys. This just cannot
* happen while adding nodes: then we would have added a leaf node
* without a value which doesn't happen. The case above can only
* occur when children are *removed* from an interior parent without
* a value and is thus handled in the trie_remove() function.
*/
child = new_node(&key[res->j], len - res->j, value);
i = __key_index(*child->key);
j = __key_to_array_index(node, *child->key);
children = node->children;
GB.Realloc((void **) &children, (node->nchildren + 1) *
sizeof(*children));
for (k = node->nchildren; k > j; k--)
children[k] = children[k - 1];
children[k] = child;
node->children = children;
node->nchildren++;
set_bit(node, i);
}
/**
* trie_insert() - Associate a value with a key in the trie
* @trie: struct trie
* @key: the key
* @len: length
* @value: the value
*
* You can use the empty string as `key' to save data in the trie's root
* node. Note that the NULL pointer is an invalid `value' and is used to
* detect value-less nodes, so don't use it!
*
* If a value was replaced, the old value is returned.
*/
void *trie_insert(struct trie *trie, const char *key, size_t len, void *value)
{
struct __trie_find_res res = __trie_find(trie, key, len);
if (res.node) {
if (__is_exact(&res, len)) {
void *last = res.node->value;
res.node->value = value;
return last;
}
__trie_insert_split(&res, key, len, value);
} else {
__trie_insert_child(&res, key, len, value);
}
return NULL;
}
/**
* __trie_remove_leaf() - Remove a leaf node
* @res: struct __trie_find_res
* @dtor: value destructor
*/
static void __trie_remove_leaf(struct __trie_find_res *res,
void (*dtor)(void *))
{
struct trie *node = res->node, *parent = res->parent;
int i, j, k;
/*
* Unlink the node which means
* a) delete it from its parent's mask and
* b) delete it from its parent's children array
*
* Then we can simply destroy it.
*/
i = __key_index(*node->key);
j = __key_to_array_index(parent, *node->key);
/*
* b) -- yes, we do b) before a) to not need to undo the mask
* changes if an allocation fails :-) and the code is different for
* each of the cases below, anyway:
* i) if the parent will have no children left, we don't bother
* doing reallocations and stuff.
* ii) if the value-less non-root parent will only have one child
* left, merge these two nodes to save space.
* iii) else, reallocate the children array normally.
*
* In i), it is impossible that the parent would have no value
* because of ii) in a former removal (just saying that every leaf
* node (except the root if it becomes a leaf) is guaranteed to have
* a value).
*/
if (parent->nchildren == 1) { /* i) */
GB.Free((void **) &parent->children);
parent->children = NULL;
parent->nchildren = 0;
/* a) */
clear_bit(parent, i);
parent->nchildren--;
/* !parent->len is equivalent to parent == trie_root */
} else if (parent->nchildren == 2 && !parent->value
&& !parent->len) { /* ii) */
struct trie *other;
if (parent->children[0] == node)
other = parent->children[1];
else
other = parent->children[0];
GB.Realloc((void **) &parent, sizeof(*parent) + parent->len
+ other->len);
memcpy(parent->key + parent->len, other->key, other->len);
parent->len += other->len;
/* does a) */
memcpy(parent->mask, other->mask, sizeof(parent->mask));
GB.Free((void **) &parent->children);
parent->children = other->children;
parent->nchildren = other->nchildren;
parent->value = other->value;
/* Do NOT destroy_node() as we copied its ->children! */
GB.Free((void **) &other);
} else { /* iii) */
/* does a) */
for (k = j + 1; k < parent->nchildren; k++)
parent->children[k - 1] = parent->children[k];
parent->nchildren--;
GB.Realloc((void **) &parent->children, parent->nchildren *
sizeof(*parent->children));
clear_bit(parent, i);
}
destroy_node(node, dtor);
}
/**
* __trie_remove_interior() - Remove an interior node
* @res: struct __trie_find_res
* @dtor: value destructor
*/
static void __trie_remove_interior(struct __trie_find_res *res,
void (*dtor)(void *))
{
/*
* Let's see: an interior node can only have 2 or more children, so
* we cannot possibly do any compression of the nodes. We just erase
* the value and leave the trie structure as-is.
*/
dtor(res->node->value);
res->node->value = NULL;
}
/**
* trie_remove() - Remove a key from the trie
* @trie: struct trie
* @key: the key
* @len: length
* @dtor: value destructor
*/
void trie_remove(struct trie *trie, const char *key, size_t len,
void (*dtor)(void *))
{
struct __trie_find_res res;
struct trie *node;
res = __trie_find(trie, key, len);
node = res.node;
/*
* We only want to work with valued, exactly-matching non-roots.
* Delete a value from the root anyways.
*/
if (!node || !__is_exact(&res, len) || !node->value)
return;
if (node == trie) {
dtor(node->value);
node->value = NULL;
return;
}
if (!node->children)
__trie_remove_leaf(&res, dtor);
else
__trie_remove_interior(&res, dtor);
}
/**
* trie_find() - Get a trie node from its key
* @trie: struct trie
* @key: the key
* @len: length
*
* Returns NULL if the key was not found. The empty string maps to the root
* node of the trie.
*/
struct trie *trie_find(const struct trie *trie, const char *key, size_t len)
{
return __trie_find_exact(trie, key, len);
}
/**
* trie_value() - Get value corresponding to a key
* @trie: struct trie
* @key: the key
* @len: length
*
* Return NULL if the key was not found. The empty string maps to the root
* node of the trie.
*/
void *trie_value(const struct trie *trie, const char *key, size_t len)
{
struct trie *node = trie_find(trie, key, len);
return node ? node->value : NULL;
}
/**
* trie_constrain() - Constrain the trie paths
* @trie: struct trie
* @p: struct trie_prefix
* @c: character
*
* To `constrain' a trie means to limit keys to a given prefix. If you have
* a trie consisting of keys "test", "tesla" and "term" and you constrain it
* with the prefix "tes", only "test" and "tesla" will be reachable.
*
* The constraint is saved in the struct trie_prefix. Contraining a trie
* does not alter its structure so that you can constrain the same trie
* multiple times simultaneously.
*
* By calling this function multiple times, you can refine the prefix in
* `p'. To begin without a constraint, use a prefix filled by
* trie_reset_prefix().
*
* If the prefix is not found, `p' is reset.
*
* WARNING
* Using any of the prefix-aware functions implies that the trie did not
* change between calls. If it did, the prefix may be invalid and the
* program may crash in consequence.
*/
void trie_constrain(const struct trie *trie, struct trie_prefix *p, char c)
{
struct trie *node;
int i;
node = p->node ? : (struct trie *) trie;
i = p->i;
if (i == node->len) {
node = get_continuation(node, c);
if (!node)
goto reset;
p->node = node;
/* node->len is guaranteed to be positive here */
p->i = 1;
} else {
if (node->key[i] != c)
goto reset;
p->i = ++i;
}
/*
* As a `logical' node counts only what has a value. If you insert
* "soup" and "sour", the resulting "sou" node would NOT exist
* logically.
*
* Additionally, the match must be exact to count as TRIE_EXACT.
*/
if (p->i == node->len && node->value)
p->state = TRIE_EXACT;
else
p->state = TRIE_EXIST;
return;
reset:
trie_reset_prefix(p);
}
/**
* trie_constrain2() - Constrain the trie multiple times
* @trie: struct trie
* @p: struct trie_prefix
* @str: string
* @len: length
*
* This function calls trie_constrain() in a loop - but with the
* difference that as soon as the prefix is not found, the function
* returns. In effect, the `str' is taken of consecutive constraints
* which should *ALL* be applied in row or none of them.
*/
void trie_constrain2(const struct trie *trie, struct trie_prefix *p,
const char *str, size_t len)
{
int i;
if (!len) {
p->state = trie->value ? TRIE_EXACT : TRIE_EXIST;
p->node = (struct trie *) trie;
p->i = 0;
return;
}
for (i = 0; i < len; i++) {
trie_constrain(trie, p, str[i]);
if (p->state == TRIE_UNSET)
return;
}
}
/**
* trie_find2() - Find a key from a trie constraint
* @trie: struct trie
* @p: struct trie_prefix
* @key: the key relative (!) to the prefix `p'
* @len: length
*
* This function is similar to trie_find(), except that keys are relative to
* the constraint in `p'.
*/
struct trie *trie_find2(const struct trie *trie,
const struct trie_prefix *p,
const char *key, size_t len)
{
struct trie *node;
int i, j;
node = p->node ? : (struct trie *) trie;
i = p->i;
/*
* First consume the rest of the prefix node. If none of the trivial
* cases occured, we can then use the normal traversal algorithm.
*/
for (j = 0; i < node->len && j < len; i++, j++)
if (node->key[i] != key[j])
return NULL;
if (j == len)
return node;
node = get_continuation(node, key[j]);
if (!node)
return NULL;
return __trie_find_exact(node, key, len);
}
/**
* trie_value2() - Analogon to trie_value() using trie_find2()
* @trie: struct trie
* @p: struct trie_prefix
* @key: the key
* @len: length
*/
void *trie_value2(const struct trie *trie, const struct trie_prefix *p,
const char *key, size_t len)
{
struct trie *node = trie_find2(trie, p, key, len);
return node ? node->value : NULL;
}