* Updated README.html, sorted list alphabetically.
<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
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>
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#!/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