* dict: initial commit
authorUrban Wallasch <urban.wallasch@freenet.de>
Sun, 3 Nov 2019 15:48:55 +0000 (16:48 +0100)
committerUrban Wallasch <urban.wallasch@freenet.de>
Sun, 3 Nov 2019 15:48:55 +0000 (16:48 +0100)
* Updated README.html, sorted list alphabetically.

README.html
dict/dict.c [new file with mode: 0644]
dict/dict.h [new file with mode: 0644]
dict/dict_test.c [new file with mode: 0644]
dict/run_test.sh [new file with mode: 0755]

index 8a72c02a4bef81597fb0e7204f4fc5a77e3571bd..1c9526412b57df3b847d553fd19390162051e96d 100644 (file)
@@ -3,10 +3,10 @@
 <table>
 
 <tr><td><a
-href="/?p=oddbits.git;a=tree;f=trace;hb=HEAD"><b>
-trace
+href="/?p=oddbits.git;a=tree;f=dict;hb=HEAD"><b>
+dict
 </b></a></td><td>-</td><td>
-C11 conforming expression tracing macro
+standard C dictionary to store data associated with unique string keys
 </td></tr>
 
 <tr><td><a
@@ -23,4 +23,11 @@ str
 standard C string processing functions to complement string.h
 </td></tr>
 
+<tr><td><a
+href="/?p=oddbits.git;a=tree;f=trace;hb=HEAD"><b>
+trace
+</b></a></td><td>-</td><td>
+C11 conforming expression tracing macro
+</td></tr>
+
 </table>
diff --git a/dict/dict.c b/dict/dict.c
new file mode 100644 (file)
index 0000000..94056ce
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * dict.c
+ *
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Dictionary to store data blobs with unique string keys in a hash table.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dict.h"
+
+
+/* Dictionary and entry types: */
+typedef
+    struct dict_entry_t
+    dict_entry_t;
+
+struct dict_entry_t {
+    char *key;
+    void *data;
+    size_t len;
+    struct dict_entry_t *next;
+};
+
+struct dict_t {
+    dict_entry_t **ht;
+    size_t htsz;
+    size_t count;
+};
+
+/* Duplicate a blob of data into allocated memory: */
+static inline void *mem_dup(const void *src, size_t n) {
+    void *p = malloc(n);
+    if (p)
+        memcpy(p, src, n);
+    return p;
+}
+
+/* Provide a strdup() implementation for strict ISO C: */
+#if defined(__STRICT_ANSI__)
+    static inline char *str_dup(const char *s) {
+        return mem_dup((void *)s, strlen(s) + 1);
+    }
+#else
+    #define str_dup(s_)  strdup(s_)
+#endif
+
+
+/* Jenkins's one_at_a_time hash; used to compute hash table index: */
+static inline int dict_hash(const char *key) {
+    size_t len = strlen(key);
+    uint32_t hash = 0;
+
+    while (len) {
+        hash += ((uint8_t *)key)[--len];
+        hash += hash << 10;
+        hash ^= hash >> 6;
+    }
+    hash += hash << 3;
+    hash ^= hash >> 11;
+    hash += hash << 15;
+    return hash;
+}
+
+/* Get entry associated with key: */
+static inline dict_entry_t *dict_getentry(dict_t *d, const char *key) {
+    dict_entry_t *e = NULL;
+
+    for (e = d->ht[dict_hash(key) % d->htsz]; e; e = e->next)
+        if (strcmp(e->key, key) == 0)
+            break;
+    return e;
+}
+
+/* Create a new dictionary entry w/o checking for existence: */
+static inline int dict_makeentry(dict_t *d, const char *key, const void *data, size_t len) {
+    dict_entry_t *e;
+    char *k;
+    void *v;
+    unsigned h;
+
+    e = malloc(sizeof *e);
+    if (!e)
+        goto ERR_1;
+    k = str_dup(key);
+    if (!k)
+        goto ERR_2;
+    v = mem_dup(data, len);
+    if (!v)
+        goto ERR_3;
+    e->key = k;
+    e->data = v;
+    e->len = len;
+    h = dict_hash(key) % d->htsz;
+    e->next = d->ht[h];
+    d->ht[h] = e;
+    d->count++;
+    return 0;
+  ERR_3:
+    free(k);
+  ERR_2:
+    free(e);
+  ERR_1:
+    return -1;
+}
+
+/*
+ * Exported functions
+ */
+
+/* Create new dictioary: */
+dict_t *dict_new(size_t ht_size) {
+    dict_t *d;
+
+    if (!ht_size) {
+        errno = EINVAL;
+        return NULL;
+    }
+    d = malloc(sizeof *d);
+    if (!d)
+        return NULL;
+    d->ht = malloc(ht_size * sizeof *d->ht);
+    if (!d->ht) {
+        free(d);
+        return NULL;
+    }
+    for (size_t i = 0; i < ht_size; ++i)
+        d->ht[i] = NULL;
+    d->htsz = ht_size;
+    d->count = 0;
+    return d;
+}
+
+/* Delete existing dictionary: */
+int dict_delete(dict_t *d) {
+    if (!d)
+        return -1;
+    for (size_t i = 0; i < d->htsz; ++i) {
+        dict_entry_t *next, *e;
+        for (e = d->ht[i]; e; e = next) {
+            next = e->next;
+            free(e->key);
+            free(e->data);
+            free(e);
+        }
+    }
+    free(d->ht);
+    free(d);
+    return 0;
+}
+
+/* Invoke cb(key, data, len, usr) for each entry in dictionary: */
+int dict_foreach(dict_t *d, dict_cb_t cb, void *usr) {
+    if (!d || !cb) {
+        errno = EINVAL;
+        return -1;
+    }
+    for (size_t i = 0; i < d->htsz; ++i ) {
+        for (dict_entry_t *e = d->ht[i]; e; e = e->next ) {
+            int r;
+            r = cb(e->key, e->data, e->len, usr);
+            if (r != 0)
+                return r;
+        }
+    }
+    return 0;
+}
+
+/* Return number of entries in dictionary: */
+size_t dict_count(dict_t *d) {
+    if (!d) {
+        errno = EINVAL;
+        return -1;
+    }
+    return d->count;
+}
+
+/* Check, if specified key exists in dictionary: */
+bool dict_keyexist(dict_t *d, const char *key) {
+    if (!d || !key) {
+        errno = EINVAL;
+        return -1;
+    }
+    return dict_getentry(d, key) != NULL;
+}
+
+/* Set or create dictionary entry for arbitrary data: */
+int dict_setraw(dict_t *d, const char *key, const void *data, size_t len) {
+    dict_entry_t *e;
+    void *v;
+
+    if (!d || !key || (!data && len)) {
+        errno = EINVAL;
+        return -1;
+    }
+    e = dict_getentry(d, key);
+    if (!e)
+        return dict_makeentry(d, key, data, len);
+
+    v = mem_dup(data, len);
+    if (!v)
+        return -1;
+    free(e->data);
+    e->data = v;
+    e->len = len;
+    return 0;
+}
+
+/* Set or create dictionary entry for string data: */
+int dict_setstr(dict_t *d, const char *key, const char *str) {
+    return dict_setraw(d, key, str, strlen(str) + 1);
+}
+
+/* Create new dictionary entry for arbitrary data: */
+int dict_addraw(dict_t *d, const char *key, const void *data, size_t len) {
+    dict_entry_t *e;
+
+    if (!d || !key || (!data && len)) {
+        errno = EINVAL;
+        return -1;
+    }
+    e = dict_getentry(d, key);
+    if (!e)
+        return dict_makeentry(d, key, data, len);
+    return -1;
+}
+
+/* Create new dictionary entry for string data: */
+int dict_addstr(dict_t *d, const char *key, const char *str) {
+    return dict_keyexist(d, key) ? -1 : dict_setstr(d, key, str);
+}
+
+/* Get pointer to data associated with key: */
+const void *dict_getraw(dict_t *d, const char *key, size_t *plen) {
+    dict_entry_t *e;
+
+    if (!d || !key) {
+        errno = EINVAL;
+        return NULL;
+    }
+    e = dict_getentry(d, key);
+    if (e) {
+        if (plen)
+            *plen = e->len;
+        return e->data;
+    }
+    return NULL;
+}
+
+/* Get pointer to string associated with key: */
+const char *dict_getstr(dict_t *d, const char *key) {
+    dict_entry_t *e;
+
+    if (!d || !key) {
+        errno = EINVAL;
+        return NULL;
+    }
+    e = dict_getentry(d, key);
+    return e ? e->data : NULL;
+}
+
+/* Remove entry from dictionary: */
+int dict_remove(dict_t *d, const char *key) {
+    dict_entry_t *prev = NULL, *e = NULL;
+    unsigned h;
+
+    if (!d || !key) {
+        errno = EINVAL;
+        return -1;
+    }
+    h = dict_hash(key) % d->htsz;
+    for (e = d->ht[h]; e; e = e->next) {
+        if (strcmp(e->key, key) == 0) {
+            if (!prev)
+                d->ht[h] = e->next;
+            else
+                prev->next = e->next;
+            free(e->key);
+            free(e->data);
+            free(e);
+            d->count--;
+            return 0;
+        }
+        prev = e;
+    }
+    return -1;
+}
+
+
+#ifdef DICT_DBG
+#include <stdio.h>
+/* Print some statistics about dictionary to stderr: */
+void dict_stat(dict_t *d) {
+    size_t min = -1, max = 0;
+
+    if (!d)
+        return;
+    fprintf(stderr, "%zu buckets\n", d->htsz);
+    fprintf(stderr, "%zu entries\n", d->count);
+    fprintf(stderr, "%.1f average\n", (double)d->count / d->htsz);
+    for (size_t i = 0; i < d->htsz; ++i ) {
+        size_t ecount;
+        ecount = 0;
+        for (dict_entry_t *e = d->ht[i]; e; e = e->next )
+            ++ecount;
+        if (ecount < min)
+            min = ecount;
+        if (ecount > max)
+            max = ecount;
+    }
+    fprintf(stderr, "%zu min\n", min);
+    fprintf(stderr, "%zu max\n", max);
+}
+#endif
diff --git a/dict/dict.h b/dict/dict.h
new file mode 100644 (file)
index 0000000..9f507e2
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * dict.h
+ *
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Dictionary to store data blobs with unique string keys in a hash table.
+ *
+ */
+
+
+#ifndef DICT_H_INCLUDED
+#define DICT_H_INCLUDED
+
+#ifdef cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+
+
+/* Opaque dictionary type: */
+typedef
+    struct dict_t
+    dict_t;
+
+/* Callback function type, see dict_foreach(): */
+typedef
+    int
+    (*dict_cb_t)(const char *, const void *, size_t, const void *);
+
+
+/*
+ * Create a new dictionary with a hash table of ht_size entries.
+ *
+ * Returns a valid dict_t pointer, or NULL, if a new dictionary could
+ * not be created.
+ */
+dict_t *dict_new(size_t ht_size);
+
+/*
+ * Delete a dictionary previously created with dict_new() and release
+ * all resources associated with it.
+ *
+ * Returns 0 on success or -1 on error.
+ */
+int dict_delete(dict_t *d);
+
+/*
+ * Iterate over dictionary and for each entry call the callback function
+ * cb with key, data pointer, data length and user supplied pointer as
+ * arguments.  The usr pointer is never dereferenced and is passed
+ * unaltered to cb.
+ *
+ * Returns 0 on success.  If on any invocation cb() returns a non-zero
+ * value, the operation is canceled immediately and dict_foreach()
+ * returns the value returned by cb.
+ */
+int dict_foreach(dict_t *d, dict_cb_t cb, void *usr);
+
+/*
+ * Returns the number of entries stored in dictionary.
+ */
+size_t dict_count(dict_t *d);
+
+/*
+ * Returns true, if the specified key exists in dictionary d, or false
+ * otherwise.
+ */
+bool dict_keyexist(dict_t *d, const char *key);
+
+/*
+ * Store a copy of data of size len associated with specified key in
+ * dictionary d.  If the key already exists, the data associated with it
+ * will be replaced; otherwise a new entry will be created.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int dict_setraw(dict_t *d, const char *key, const void *data, size_t len);
+int dict_setstr(dict_t *d, const char *key, const char *str);
+
+/*
+ * Add a copy of data of size len as a new entry to dictionary d.  The
+ * operation fails, if the specified key already exists.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int dict_addraw(dict_t *d, const char *key, const void *data, size_t len);
+int dict_addstr(dict_t *d, const char *key, const char *str);
+
+/*
+ * Retrieve a pointer to the data associated with key from dictionary d.
+ *
+ * Returns a valid pointer on success, or NULL on error.
+ */
+const void *dict_getraw(dict_t *d, const char *key, size_t *plen);
+const char *dict_getstr(dict_t *d, const char *key);
+
+/*
+ * Remove the entry associated with key from dictionary d.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int dict_remove(dict_t *d, const char *key);
+
+
+#ifdef cplusplus
+}
+#endif
+
+#endif /* ndef DICT_H_INCLUDED */
diff --git a/dict/dict_test.c b/dict/dict_test.c
new file mode 100644 (file)
index 0000000..25567b7
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * dict_test.c
+ *
+ * Copyright (c) 2019, Urban Wallasch
+ * BSD 3-Clause License, see LICENSE file for more details.
+ *
+ * Simple, non-exhaustive quick check for dict.[ch].
+ *
+ * Build with:
+ * gcc -std=c99 -Wpedantic -Wall -Wextra -Werror -O2 -odict_test dict.c dict_test.c
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "dict.h"
+
+#ifdef DICT_DBG
+    void dict_stat(dict_t *d);
+#else
+    #define dict_stat(d_)
+#endif
+
+int dump_cb(const char *k, const void *v, size_t n, const void *u) {
+    int *pi = (int *)u;
+    printf("%3d: '%s' '%s' %zu\n", *pi, k, (char *)v, n);
+    ++*pi;
+    return 0;
+}
+
+int main(void) {
+    int cnt;
+    dict_t *d;
+    size_t l;
+
+    d = dict_new(100);
+    assert(d != NULL);
+    assert( dict_addstr(d, "foo", "bar") == 0 );
+    assert( dict_addstr(d, "foo", "gnampf") < 0 );
+    assert( strcmp("bar", dict_getstr(d, "foo")) == 0 );
+    assert( dict_remove(d, "foo") == 0 );
+    assert( dict_getstr(d, "foo") == NULL );
+    assert( dict_addstr(d, "foo", "fred") == 0 );
+    assert( strcmp("fred", dict_getstr(d, "foo")) == 0 );
+    assert( dict_setstr(d, "foo", "mogura") == 0 );
+    assert( strcmp("mogura", dict_getstr(d, "foo")) == 0 );
+    assert( dict_setraw(d, "foo", "baz\0\x1\x2\x3\0\0\0", 10) == 0 );
+    l = -1;
+    assert( memcmp("baz\0\x1\x2\x3\0\0\0", dict_getraw(d, "foo", &l), 10) == 0);
+    assert( l == 10 );
+    assert( dict_remove(d, "foo") == 0 );
+    assert( dict_delete(d) == 0 );
+
+    d = dict_new(2048);
+    assert(d != NULL);
+    for (unsigned i = 0; i < 300000; ++i) {
+        char k[100], s[100];
+        sprintf(k, "%u %x", i, i);
+        sprintf(s, "%08x", i);
+        assert( dict_addstr(d, k, s) == 0);
+    }
+    cnt = 0;
+    assert( dict_foreach( d, dump_cb, &cnt) == 0);
+    dict_stat(d);
+    for (unsigned i = 0; i < 300000; ++i) {
+        char k[100];
+        sprintf(k, "%u %x", i, i);
+        dict_remove(d, k);
+    }
+    assert(dict_count(d) == 0);
+    cnt = 0;
+    assert( dict_foreach( d, dump_cb, &cnt) == 0);
+    dict_stat(d);
+    assert( dict_delete(d) == 0 );
+
+    return 0;
+}
diff --git a/dict/run_test.sh b/dict/run_test.sh
new file mode 100755 (executable)
index 0000000..4a61434
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/bash
+EXEC=dict_test
+SRC="dict.c dict_test.c"
+OPTS="-std=c99 -Wpedantic -Wall -Wextra -Werror -O2 -DDICT_DBG"
+
+CMD="gcc $OPTS -o $EXEC $SRC"
+echo $CMD
+$CMD && time "./$EXEC" > /dev/null