aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--map.c298
-rw-r--r--map.h34
2 files changed, 332 insertions, 0 deletions
diff --git a/map.c b/map.c
new file mode 100644
index 0000000..75dcc36
--- /dev/null
+++ b/map.c
@@ -0,0 +1,298 @@
+/* Crit-bit tree based map which supports lookups based on unique
+ * prefixes as well as ordered iteration.
+ *
+ * Based on public domain code from Rusty Russel, Adam Langley
+ * and D. J. Bernstein.
+ *
+ * Further information about the data structure can be found at:
+ * http://cr.yp.to/critbit.htm
+ * http://github.com/agl/critbit
+ * http://ccodearchive.net/info/strmap.html
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include "map.h"
+
+typedef struct Node Node;
+
+struct Map { /* struct holding either an item with value v and key u.s or an internal node */
+ union {
+ Node *n;
+ const char *s;
+ } u;
+ void *v; /* value stored in the map, if non NULL u.s holds the corresponding key */
+};
+
+struct Node {
+ Map child[2]; /* These point to strings or nodes. */
+ size_t byte_num; /* The byte number where first bit differs. */
+ uint8_t bit_num; /* The bit where these children differ. */
+};
+
+/* Closest key to this in a non-empty map. */
+static Map *closest(Map *n, const char *key)
+{
+ size_t len = strlen(key);
+ const uint8_t *bytes = (const uint8_t *)key;
+
+ /* Anything with NULL value is an internal node. */
+ while (!n->v) {
+ uint8_t direction = 0;
+
+ if (n->u.n->byte_num < len) {
+ uint8_t c = bytes[n->u.n->byte_num];
+ direction = (c >> n->u.n->bit_num) & 1;
+ }
+ n = &n->u.n->child[direction];
+ }
+ return n;
+}
+
+void *map_get(const Map *map, const char *key)
+{
+ /* Not empty map? */
+ if (map->u.n) {
+ Map *n = closest((Map *)map, key);
+ if (strcmp(key, n->u.s) == 0)
+ return n->v;
+ }
+ return NULL;
+}
+
+void *map_closest(const Map *map, const char *prefix)
+{
+ errno = 0;
+ void *v = map_get(map, prefix);
+ if (v)
+ return v;
+ const Map *m = map_prefix(map, prefix);
+ if (!m->v)
+ errno = ENOENT;
+ return m->v;
+}
+
+bool map_put(Map *map, const char *k, const void *value)
+{
+ size_t len = strlen(k);
+ const uint8_t *bytes = (const uint8_t *)k;
+ Map *n;
+ Node *newn;
+ size_t byte_num;
+ uint8_t bit_num, new_dir;
+ char *key;
+
+ if (!value) {
+ errno = EINVAL;
+ return false;
+ }
+
+ if (!(key = strdup(k))) {
+ errno = ENOMEM;
+ return false;
+ }
+
+ /* Empty map? */
+ if (!map->u.n) {
+ map->u.s = key;
+ map->v = (void *)value;
+ return true;
+ }
+
+ /* Find closest existing key. */
+ n = closest(map, key);
+
+ /* Find where they differ. */
+ for (byte_num = 0; n->u.s[byte_num] == key[byte_num]; byte_num++) {
+ if (key[byte_num] == '\0') {
+ /* All identical! */
+ free(key);
+ return false;
+ }
+ }
+
+ /* Find which bit differs */
+ uint8_t diff = (uint8_t)n->u.s[byte_num] ^ bytes[byte_num];
+ /* TODO: bit_num = 31 - __builtin_clz(diff); ? */
+ for (bit_num = 0; diff >>= 1; bit_num++);
+
+ /* Which direction do we go at this bit? */
+ new_dir = ((bytes[byte_num]) >> bit_num) & 1;
+
+ /* Allocate new node. */
+ newn = malloc(sizeof(*newn));
+ if (!newn) {
+ free(key);
+ errno = ENOMEM;
+ return false;
+ }
+ newn->byte_num = byte_num;
+ newn->bit_num = bit_num;
+ newn->child[new_dir].v = (void *)value;
+ newn->child[new_dir].u.s = key;
+
+ /* Find where to insert: not closest, but first which differs! */
+ n = map;
+ while (!n->v) {
+ uint8_t direction = 0;
+
+ if (n->u.n->byte_num > byte_num)
+ break;
+ /* Subtle: bit numbers are "backwards" for comparison */
+ if (n->u.n->byte_num == byte_num && n->u.n->bit_num < bit_num)
+ break;
+
+ if (n->u.n->byte_num < len) {
+ uint8_t c = bytes[n->u.n->byte_num];
+ direction = (c >> n->u.n->bit_num) & 1;
+ }
+ n = &n->u.n->child[direction];
+ }
+
+ newn->child[!new_dir] = *n;
+ n->u.n = newn;
+ n->v = NULL;
+ return true;
+}
+
+void *map_delete(Map *map, const char *key)
+{
+ size_t len = strlen(key);
+ const uint8_t *bytes = (const uint8_t *)key;
+ Map *parent = NULL, *n;
+ void *value = NULL;
+ uint8_t direction;
+
+ /* Empty map? */
+ if (!map->u.n) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ /* Find closest, but keep track of parent. */
+ n = map;
+ /* Anything with NULL value is a node. */
+ while (!n->v) {
+ uint8_t c = 0;
+
+ parent = n;
+ if (n->u.n->byte_num < len) {
+ c = bytes[n->u.n->byte_num];
+ direction = (c >> n->u.n->bit_num) & 1;
+ } else {
+ direction = 0;
+ }
+ n = &n->u.n->child[direction];
+ }
+
+ /* Did we find it? */
+ if (strcmp(key, n->u.s)) {
+ errno = ENOENT;
+ return false;
+ }
+
+ free((char*)n->u.s);
+ value = n->v;
+
+ if (!parent) {
+ /* We deleted last node. */
+ map->u.n = NULL;
+ } else {
+ Node *old = parent->u.n;
+ /* Raise other node to parent. */
+ *parent = old->child[!direction];
+ free(old);
+ }
+
+ return value;
+}
+
+static bool iterate(Map n, bool (*handle)(const char *, void *, void *), const void *data)
+{
+ if (n.v)
+ return handle(n.u.s, n.v, (void *)data);
+
+ return iterate(n.u.n->child[0], handle, data)
+ && iterate(n.u.n->child[1], handle, data);
+}
+
+void map_iterate(const Map *map, bool (*handle)(const char *, void *, void *), const void *data)
+{
+ /* Empty map? */
+ if (!map->u.n)
+ return;
+
+ iterate(*map, handle, data);
+}
+
+const Map *map_prefix(const Map *map, const char *prefix)
+{
+ const Map *n, *top;
+ size_t len = strlen(prefix);
+ const uint8_t *bytes = (const uint8_t *)prefix;
+
+ /* Empty map -> return empty map. */
+ if (!map->u.n)
+ return map;
+
+ top = n = map;
+
+ /* We walk to find the top, but keep going to check prefix matches. */
+ while (!n->v) {
+ uint8_t c = 0, direction;
+
+ if (n->u.n->byte_num < len)
+ c = bytes[n->u.n->byte_num];
+
+ direction = (c >> n->u.n->bit_num) & 1;
+ n = &n->u.n->child[direction];
+ if (c)
+ top = n;
+ }
+
+ if (strncmp(n->u.s, prefix, len)) {
+ /* Convenient return for prefixes which do not appear in map. */
+ static const Map empty_map;
+ return &empty_map;
+ }
+
+ return top;
+}
+
+static void clear(Map n)
+{
+ if (!n.v) {
+ clear(n.u.n->child[0]);
+ clear(n.u.n->child[1]);
+ free(n.u.n);
+ } else {
+ free((char*)n.u.s);
+ }
+}
+
+void map_clear(Map *map)
+{
+ if (map->u.n)
+ clear(*map);
+ map->u.n = NULL;
+ map->v = NULL;
+}
+
+bool map_empty(const Map *map)
+{
+ return map->u.n == NULL;
+}
+
+Map *map_new(void)
+{
+ return calloc(1, sizeof(Map));
+}
+
+void map_free(Map *map)
+{
+ if (!map)
+ return;
+ map_clear(map);
+ free(map);
+}
diff --git a/map.h b/map.h
new file mode 100644
index 0000000..ed42393
--- /dev/null
+++ b/map.h
@@ -0,0 +1,34 @@
+#ifndef MAP_H
+#define MAP_H
+
+#include <stdbool.h>
+
+typedef struct Map Map;
+
+/* Allocate a new map. */
+Map *map_new(void);
+/* Retrieves a value, or NULL if it isn't in the map */
+void *map_get(const Map*, const char *key);
+/* Returns the corresponding value if the given prefix is unique.
+ * Otherwise NULL, if no such prefix exists then errno is set to ENOENT. */
+void *map_closest(const Map*, const char *prefix);
+/* Place a member in the map. This returns false if we run out of memory
+ * (errno = ENOMEM), or if that key already appears in the map (errno = EEXIST). */
+bool map_put(Map*, const char *key, const void *value);
+/* Remove a member from the map. Returns the removed entry or NULL
+ * if there was no entry found using the given key*/
+void *map_delete(Map*, const char *key);
+/* Ordered iteration over a map, call handle for every entry. If handle
+ * returns false, the iteration will stop. */
+void map_iterate(const Map*, bool (*handle)(const char *key, void *value, void *data), const void *data);
+/* Return a submap matching a prefix. This returns a pointer into the
+ * original map, so don't alter the map while using the return value. */
+const Map *map_prefix(const Map*, const char *prefix);
+/* Test whether the map is empty i.e. contains no elements */
+bool map_empty(const Map*);
+/* Remove every member from the map. The map will be empty after this. */
+void map_clear(Map*);
+/* Release all memory associated with this map */
+void map_free(Map*);
+
+#endif