Add hashtable and work-in-progress bigraph modules.
authorDavid Given <dg@cowlark.com>
Thu, 15 Dec 2016 23:32:22 +0000 (00:32 +0100)
committerDavid Given <dg@cowlark.com>
Thu, 15 Dec 2016 23:32:22 +0000 (00:32 +0100)
modules/src/data/bigraph.c [new file with mode: 0644]
modules/src/data/bigraph.h [new file with mode: 0644]
modules/src/data/hashtable.c [new file with mode: 0644]
modules/src/data/hashtable.h [new file with mode: 0644]

diff --git a/modules/src/data/bigraph.c b/modules/src/data/bigraph.c
new file mode 100644 (file)
index 0000000..d172a74
--- /dev/null
@@ -0,0 +1,228 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include "bigraph.h"
+
+struct edgenode
+{
+    struct edgenode* next;
+    struct vertex* this;
+    struct vertex* other;
+};
+
+struct vertex
+{
+    void* data;
+    struct edgenode* connections;
+    int degree;
+};
+
+static uint32_t edge_hash_function(void* key)
+{
+    struct edgenode* en = key;
+    /* This will always return the same value, even if the two endpoints are swapped. */
+    return standard_pointer_hash_function(en->this) ^ standard_pointer_hash_function(en->other);
+}
+
+static bool edge_comparison_function(void* key1, void* key2)
+{
+    struct edgenode* en1 = key1;
+    struct edgenode* en2 = key2;
+
+    return ((en1->this == en2->this) && (en1->other == en2->other))
+        || ((en1->this == en2->other) && (en1->other == en2->this));
+}
+
+static void lazy_init(struct graph* g)
+{
+    g->edges.hashfunction = edge_hash_function;
+    g->edges.cmpfunction = edge_comparison_function;
+}
+
+void graph_reset(struct graph* g)
+{
+    lazy_init(g);
+
+    for (;;)
+    {
+        struct vertex* vertex = hashtable_pop(&g->vertices);
+        if (!vertex)
+            return;
+
+        while (vertex->connections)
+        {
+            struct edgenode* next = vertex->connections->next;
+            free(vertex->connections);
+            vertex->connections = next;
+        }
+
+        free(vertex);
+    }
+}
+
+bool graph_contains_vertex(struct graph* g, void* data)
+{
+    lazy_init(g);
+
+    return hashtable_contains(&g->vertices, data);
+}
+
+static struct vertex* find_or_add_vertex(struct graph* g, void* data)
+{
+    struct vertex* vertex;
+
+    lazy_init(g);
+
+    vertex = hashtable_get(&g->vertices, data);
+    if (!vertex)
+    {
+        vertex = calloc(1, sizeof(struct vertex));
+        vertex->data = data;
+        hashtable_put(&g->vertices, data, vertex);
+    }
+    
+    return vertex;
+}
+
+static struct edgenode** find_edgep(struct vertex* v1, struct vertex* v2)
+{
+    struct edgenode** ep = &v1->connections;
+
+    while (*ep)
+    {
+        if ((*ep)->other == v2)
+            return ep;
+        ep = &(*ep)->next;
+    }
+
+    return ep;
+}
+
+static struct edgenode* add_edge(struct vertex* v1, struct vertex* v2)
+{
+    struct edgenode** ep = find_edgep(v1, v2);
+
+    if (!*ep)
+    {
+        *ep = calloc(1, sizeof(struct edgenode));
+        (*ep)->this = v1;
+        (*ep)->other = v2;
+        v1->degree++;
+    }
+
+    return *ep;
+}
+
+void graph_add_edge(struct graph* g, void* data1, void* data2)
+{
+    struct vertex* v1 = find_or_add_vertex(g, data1);
+    struct vertex* v2 = find_or_add_vertex(g, data2);
+    struct edgenode* e;
+
+    add_edge(v1, v2);
+    e = add_edge(v2, v1);
+
+    hashtable_put(&g->edges, e, e);
+}
+
+static struct edgenode* remove_edge(struct vertex* v1, struct vertex* v2)
+{
+    struct edgenode** ep = find_edgep(v1, v2);
+
+    if (*ep)
+    {
+        struct edgenode* old = *ep;
+        *ep = (*ep)->next;
+        v1->degree--;
+        return old;
+    }
+
+    return NULL;
+}
+
+void graph_remove_edge(struct graph* g, void* data1, void* data2)
+{
+    struct vertex* v1 = find_or_add_vertex(g, data1);
+    struct vertex* v2 = find_or_add_vertex(g, data2);
+    struct edgenode* e1 = remove_edge(v1, v2);
+    struct edgenode* e2 = remove_edge(v2, v1);
+
+    assert(!e1 == !e2);
+
+    if (e1)
+    {
+        /* e1 is a template; the actual object in the hashtable might actually
+         * be e2 (as they compare the same). So, remove from the hashtable
+         * before freeing anything. */
+        hashtable_remove(&g->edges, e1);
+        free(e1);
+    }
+    if (e2)
+        free(e2);
+}
+
+void graph_add_vertex(struct graph* g, void* data)
+{
+    find_or_add_vertex(g, data);
+}
+
+void graph_remove_vertex(struct graph* g, void* data)
+{
+    struct vertex* vertex;
+
+    lazy_init(g);
+    vertex = hashtable_get(&g->vertices, data);
+    if (!vertex)
+        return;
+    
+    while (vertex->connections)
+    {
+        struct edgenode* next = vertex->connections->next;
+        struct edgenode* e = remove_edge(vertex->connections->other, vertex);
+        hashtable_remove(&g->edges, vertex->connections);
+        free(e);
+        free(vertex->connections);
+        vertex->connections = next;
+    }
+
+    hashtable_remove(&g->vertices, data);
+}
+
+int graph_get_vertex_degree(struct graph* g, void* data)
+{
+    struct vertex* vertex;
+
+    lazy_init(g);
+    vertex = hashtable_get(&g->vertices, data);
+    assert(vertex);
+    return vertex->degree;
+}
+
+void* graph_next_vertex(struct graph* g, struct vertex_iterator* it)
+{
+    struct vertex* vertex = hashtable_next(&g->vertices, &it->hit);
+    if (vertex)
+    {
+        it->data = vertex->data;
+        return vertex->data;
+    }
+    else
+        return NULL;
+}
+
+bool graph_next_edge(struct graph* g, struct edge_iterator* it)
+{
+    struct edgenode* e = hashtable_next(&g->edges, &it->hit);
+    if (e)
+    {
+        it->left = e->this->data;
+        it->right = e->other->data;
+        return true;
+    }
+    else
+    {
+        it->left = it->right = NULL;
+        return false;
+    }
+}
diff --git a/modules/src/data/bigraph.h b/modules/src/data/bigraph.h
new file mode 100644 (file)
index 0000000..ff6f420
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef BIGRAPH_H
+#define BIGRAPH_H
+
+#include "hashtable.h"
+
+/* A bidirectional graph with node addition and removal capabilities. */
+
+struct graph
+{
+    struct hashtable vertices;
+    struct hashtable edges;
+};
+
+struct vertex_iterator
+{
+    /* Public */
+    void* data;
+
+    /* Private */
+    struct hashtable_iterator hit;
+};
+
+struct edge_iterator
+{
+    /* Public */
+    void* left;
+    void* right;
+
+    /* Private */
+    struct hashtable_iterator hit;
+};
+
+extern void graph_reset(struct graph* g);
+
+extern bool graph_contains_vertex(struct graph* g, void* data);
+extern void graph_add_vertex(struct graph* g, void* data);
+extern void graph_remove_vertex(struct graph* g, void* data);
+extern void graph_add_edge(struct graph* g, void* data1, void* data2);
+extern void graph_remove_edge(struct graph* g, void* data1, void* data2);
+extern int graph_get_vertex_degree(struct graph* g, void* data);
+
+extern void* graph_next_vertex(struct graph* g, struct vertex_iterator* it);
+extern bool graph_next_edge(struct graph* g, struct edge_iterator* it);
+
+#endif
diff --git a/modules/src/data/hashtable.c b/modules/src/data/hashtable.c
new file mode 100644 (file)
index 0000000..ebaae3b
--- /dev/null
@@ -0,0 +1,158 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "hashtable.h"
+
+struct hashnode
+{
+    struct hashnode* next;
+    void* key;
+    void* data;
+};
+
+uint32_t standard_pointer_hash_function(void* key)
+{
+    /* Basile-Starynkevitch pointer hash */
+    uintptr_t ptr = (uintptr_t) key;
+    return (uint32_t) ((13 * ptr) ^ (ptr >> 15));
+}
+
+bool standard_pointer_comparison_function(void* key1, void* key2)
+{
+    return (key1 == key2);
+}
+
+static void lazy_init(struct hashtable* ht)
+{
+    if (!ht->num_buckets)
+        ht->num_buckets = 37;
+
+    if (!ht->hashfunction)
+        ht->hashfunction = standard_pointer_hash_function;
+
+    if (!ht->cmpfunction)
+        ht->cmpfunction = standard_pointer_comparison_function;
+
+    if (!ht->buckets)
+        ht->buckets = calloc(ht->num_buckets, sizeof(struct hashnode*));
+}
+
+void hashtable_reset(struct hashtable* ht)
+{
+    while (ht->size)
+        hashtable_pop(ht);
+
+    free(ht->buckets);
+    ht->buckets = NULL;
+}
+
+static struct hashnode** findnodep(struct hashtable* ht, void* key)
+{
+    uint32_t hash;
+    struct hashnode** hnp;
+
+    lazy_init(ht);
+    hash = ht->hashfunction(key) % ht->num_buckets;
+    hnp = &ht->buckets[hash];
+
+    while (*hnp && !ht->cmpfunction((*hnp)->key, key))
+        hnp = &(*hnp)->next;
+
+    return hnp;
+}
+
+void* hashtable_put(struct hashtable* ht, void* key, void* data)
+{
+    void* olddata;
+    struct hashnode** hnp = findnodep(ht, key);
+    if (!*hnp)
+    {
+        *hnp = calloc(1, sizeof(struct hashnode));
+        ht->size++;
+    }
+
+    olddata = (*hnp)->data;
+    (*hnp)->key = key;
+    (*hnp)->data = data;
+    return olddata;
+}
+
+void* hashtable_get(struct hashtable* ht, void* key)
+{
+    struct hashnode** hnp = findnodep(ht, key);
+    if (*hnp)
+        return (*hnp)->data;
+    return NULL;
+}
+
+bool hashtable_contains(struct hashtable* ht, void* key)
+{
+    return *findnodep(ht, key);
+}
+
+bool hashtable_remove(struct hashtable* ht, void* key)
+{
+    struct hashnode** hnp = findnodep(ht, key);
+    if (*hnp)
+    {
+        struct hashnode* hn = *hnp;
+        *hnp = hn->next;
+        free(hn);
+        ht->size--;
+        return true;
+    }
+
+    return false;
+}
+
+void* hashtable_pop(struct hashtable* ht)
+{
+    int i;
+
+    if (ht->size == 0)
+        return NULL;
+    
+    lazy_init(ht);
+    for (i=0; i<ht->num_buckets; i++)
+    {
+        struct hashnode** hnp = &ht->buckets[i];
+        if (*hnp)
+        {
+            struct hashnode* hn = *hnp;
+            void* data = hn->data;
+            *hnp = hn->next;
+            free(hn);
+            ht->size--;
+            return data;
+        }
+    }
+
+    return NULL;
+}
+
+void* hashtable_next(struct hashtable* ht, struct hashtable_iterator* it)
+{
+    while (!it->node)
+    {
+        if (it->bucket == ht->num_buckets)
+        {
+            it->data = NULL;
+            it->bucket = 0;
+            it->node = NULL;
+            return NULL;
+        }
+
+        it->node = ht->buckets[it->bucket];
+        if (it->node)
+            break;
+
+        it->bucket++;
+    }
+
+    it->data = it->node->data;
+    it->node = it->node->next;
+    if (!it->node)
+        it->bucket++;
+
+    return it->data;
+}
diff --git a/modules/src/data/hashtable.h b/modules/src/data/hashtable.h
new file mode 100644 (file)
index 0000000..dac58c0
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef HASHTABLE_H
+#define HASHTABLE_H
+
+/* A simple, autoresizing hash table. */
+
+typedef uint32_t hashfunction_t(void* key);
+typedef bool cmpfunction_t(void* key1, void* key2);
+
+extern uint32_t standard_pointer_hash_function(void* key);
+extern bool standard_pointer_comparison_function(void* key1, void* key2);
+
+struct hashtable
+{
+    unsigned int num_buckets;
+    hashfunction_t* hashfunction;
+    cmpfunction_t* cmpfunction;
+    struct hashnode** buckets;
+    int size;
+};
+
+struct hashtable_iterator
+{
+    /* Public */
+    void* data;
+
+    /* Private */
+    int bucket;
+    struct hashnode* node;
+};
+
+extern void hashtable_reset(struct hashtable* ht);
+
+extern void* hashtable_put(struct hashtable* ht, void* key, void* data);
+extern void* hashtable_get(struct hashtable* ht, void* key);
+extern bool hashtable_contains(struct hashtable* ht, void* key);
+extern bool hashtable_remove(struct hashtable* ht, void* key);
+extern void* hashtable_pop(struct hashtable* ht);
+extern void* hashtable_next(struct hashtable* ht, struct hashtable_iterator* it);
+
+#endif