From: Urban Wallasch Date: Sun, 3 Nov 2019 15:48:55 +0000 (+0100) Subject: * dict: initial commit X-Git-Url: https://git.packet-gain.de/?a=commitdiff_plain;h=5c0350946e7022a848cee363b41c625b8399703b;p=oddbits.git * dict: initial commit * Updated README.html, sorted list alphabetically. --- diff --git a/README.html b/README.html index 8a72c02..1c95264 100644 --- a/README.html +++ b/README.html @@ -3,10 +3,10 @@ + +
-trace +href="/?p=oddbits.git;a=tree;f=dict;hb=HEAD"> +dict - -C11 conforming expression tracing macro +standard C dictionary to store data associated with unique string keys
+trace +- +C11 conforming expression tracing macro +
diff --git a/dict/dict.c b/dict/dict.c new file mode 100644 index 0000000..94056ce --- /dev/null +++ b/dict/dict.c @@ -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 +#include +#include +#include + +#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 +/* 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 index 0000000..9f507e2 --- /dev/null +++ b/dict/dict.h @@ -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 +#include + + +/* 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 index 0000000..25567b7 --- /dev/null +++ b/dict/dict_test.c @@ -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 +#include +#include + +#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 index 0000000..4a61434 --- /dev/null +++ b/dict/run_test.sh @@ -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