From b65394ab50d863871fb3104b57b4aa90da276973 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sun, 16 Jul 2023 01:12:56 +0000 Subject: [PATCH] Add basic Graph API. This is going to be useful with state resolution and dependency ordering, both of which will be crutial components of Telodendria. --- Cytoplasm/src/Graph.c | 347 ++++++++++++++++++++++++++++++++++ Cytoplasm/src/include/Graph.h | 175 +++++++++++++++++ 2 files changed, 522 insertions(+) create mode 100644 Cytoplasm/src/Graph.c create mode 100644 Cytoplasm/src/include/Graph.h diff --git a/Cytoplasm/src/Graph.c b/Cytoplasm/src/Graph.c new file mode 100644 index 0000000..a4d63ed --- /dev/null +++ b/Cytoplasm/src/Graph.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#include + +#include + +struct Graph +{ + size_t n; + Edge *matrix; +}; + +Graph * +GraphCreate(size_t n) +{ + Graph *g; + + if (!n) + { + return NULL; + } + + g = Malloc(sizeof(Graph)); + if (!g) + { + return NULL; + } + + g->n = n; + + g->matrix = Malloc((n * n) * sizeof(Edge)); + if (!g->matrix) + { + Free(g); + return NULL; + } + + memset(g->matrix, 0, (n * n) * sizeof(Edge)); + + return g; +} + +Graph * +GraphCreateWithEdges(size_t n, Edge * matrix) +{ + Graph *g = GraphCreate(n); + + if (!g) + { + return NULL; + } + + memcpy(g->matrix, matrix, (n * n) * sizeof(Edge)); + + return g; +} + +void +GraphFree(Graph * g) +{ + if (!g) + { + return; + } + + Free(g->matrix); + Free(g); +} + +Edge +GraphEdgeGet(Graph * g, Node n1, Node n2) +{ + if (n1 >= g->n || n2 >= g->n) + { + return -1; + } + + return g->matrix[(g->n * n1) + n2]; +} + +Edge +GraphEdgeSet(Graph * g, Node n1, Node n2, Edge e) +{ + int oldVal; + + if (n1 >= g->n || n2 >= g->n) + { + return -1; + } + + if (e < 0) + { + return -1; + } + + oldVal = g->matrix[(g->n * n1) + n2]; + + g->matrix[(g->n * n1) + n2] = e; + + return oldVal; +} + +size_t +GraphCountNodes(Graph * g) +{ + return g ? g->n : 0; +} + +Node * +GraphBreadthFirstSearch(Graph * G, Node s, size_t * n) +{ + Node *visited; + Node *queue; + Node *result; + size_t queueSize; + Node i; + + if (!G || !n) + { + return NULL; + } + + *n = 0; + + result = Malloc(G->n * sizeof(Node)); + if (!result) + { + return NULL; + } + + if (s >= G->n) + { + Free(result); + return NULL; + } + + visited = Malloc(G->n * sizeof(Node)); + memset(visited, 0, G->n * sizeof(Node)); + queue = Malloc(G->n * sizeof(Node)); + queueSize = 0; + + visited[s] = 1; + + queueSize++; + queue[queueSize - 1] = s; + + while (queueSize) + { + s = queue[queueSize - 1]; + queueSize--; + + result[*n] = s; + (*n)++; + + for (i = 0; i < G->n; i++) + { + if (GraphEdgeGet(G, s, i) && !visited[i]) + { + visited[i] = 1; + + queueSize++; + queue[queueSize - 1] = i; + } + } + } + + Free(visited); + Free(queue); + + return result; +} + +static void +GraphDepthFirstSearchRecursive(Graph * G, Node s, Node * result, size_t * n, + Node * visited) +{ + size_t i; + + visited[s] = 1; + + result[*n] = s; + (*n)++; + + for (i = 0; i < G->n; i++) + { + if (GraphEdgeGet(G, s, i) && !visited[i]) + { + GraphDepthFirstSearchRecursive(G, i, result, n, visited); + } + } +} + +Node * +GraphDepthFirstSearch(Graph * G, Node s, size_t * n) +{ + Node *visited; + Node *result; + + if (!G || !n) + { + return NULL; + } + + result = Malloc(G->n * sizeof(Node)); + if (!result) + { + return NULL; + } + + *n = 0; + + if (s >= G->n) + { + Free(result); + return NULL; + } + + visited = Malloc(G->n * sizeof(Node)); + memset(visited, 0, G->n * sizeof(Node)); + + GraphDepthFirstSearchRecursive(G, s, result, n, visited); + + Free(visited); + + return result; +} + +static void +GraphTopologicalSortRecursive(Graph * G, Node s, Node * visited, + Node * stack, size_t * stackSize) +{ + size_t i; + + visited[s] = 1; + + for (i = 0; i < G->n; i++) + { + if (GraphEdgeGet(G, s, i) && !visited[i]) + { + GraphTopologicalSortRecursive(G, i, visited, stack, stackSize); + } + } + + stack[*stackSize] = s; + (*stackSize)++; +} + +Node * +GraphTopologicalSort(Graph * G, size_t * n) +{ + Node *visited; + Node *stack; + Node *result; + + size_t i; + size_t stackSize; + + if (!G || !n) + { + return NULL; + } + + *n = 0; + + result = Malloc(G->n * sizeof(Node)); + if (!result) + { + return NULL; + } + + visited = Malloc(G->n * sizeof(Node)); + memset(visited, 0, G->n * sizeof(Node)); + stack = Malloc(G->n * sizeof(Node)); + memset(stack, 0, G->n * sizeof(Node)); + + stackSize = 0; + + for (i = 0; i < G->n; i++) + { + if (!visited[i]) + { + GraphTopologicalSortRecursive(G, i, visited, stack, &stackSize); + } + } + + Free(visited); + + while (stackSize) + { + stackSize--; + result[*n] = stack[stackSize]; + (*n)++; + } + + Free(stack); + + return result; +} + +Graph * +GraphTranspose(Graph * G) +{ + Graph *T = Malloc(sizeof(Graph)); + size_t i, j; + + T->n = G->n; + T->matrix = Malloc((G->n * G->n) * sizeof(Edge)); + + memset(T->matrix, 0, (T->n * T->n) * sizeof(Edge)); + + for (i = 0; i < G->n; i++) + { + for (j = 0; j < G->n; j++) + { + if (GraphEdgeGet(G, i, j)) + { + GraphEdgeSet(T, j, i, GraphEdgeGet(G, i, j)); + } + } + } + + return T; +} diff --git a/Cytoplasm/src/include/Graph.h b/Cytoplasm/src/include/Graph.h new file mode 100644 index 0000000..02e4e02 --- /dev/null +++ b/Cytoplasm/src/include/Graph.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_GRAPH_H +#define CYTOPLASM_GRAPH_H + +/*** + * @Nm Graph + * @Nd Extremely simple graph, implemented as an adjacency matrix. + * @Dd July 15 2023 + * + * .Nm + * is a basic graph data structure originally written for a computer + * science class on data structures and algorithms, in which it + * received full credit. This is an adaptation of the original + * implementation that follows the Cytoplasm style and uses Cytoplasm + * APIs when convenient. + * .P + * .Nm + * stores data in an adjacency matrix, which means the storage + * complexity is O(N^2), where N is the number of vertices (called + * Nodes in this implementation) in the graph. However, this makes the + * algorithms fast and efficient. + * .P + * Nodes are identified by index, so the first node is 0, the second + * is 1, and so on. This data structure does not support storing + * arbitrary data as nodes; rather, the intended use case is to add + * all your node data to an Array, thus giving each node an index, + * and then manipulating the graph with that index. This allows access + * to node data in O(1) time in call cases, and is the most memory + * efficient. + * .P + * .Nm + * can be used to store a variety of types of graphs, although it is + * primarily suited to directed and weighted graphs. + */ + +#include + +/** + * The functions provided here operate on an opaque graph structure. + * This structure really just stores a matrix in a contiguous block of + * memory, as well as the number of nodes in the graph, but the + * structure is kept opaque so that it remains internally consistent. + * It also maintains the style of the Cytoplasm library. + */ +typedef struct Graph Graph; + +/** + * An Edge is really just a weight, which is easily represented by an + * integer. However, it makes sense to alias this to Edge for clarity, + * both in the documentation and in the implementation. + */ +typedef int Edge; + +/** + * A Node is really just a row or column in the matrix, which is easily + * represented by an unsigned integer. However, it makes sense to alias + * this to Node for clarity, both in the documentation and the + * implementation. + */ +typedef size_t Node; + +/** + * Create a new graph structure with the given number of vertices. + */ +extern Graph *GraphCreate(size_t); + +/** + * Create a new graph data structure with the given number of vertices + * and the given adjacency matrix. The adjacency matrix is copied + * verbatim into the graph data structure without any validation. + */ +extern Graph *GraphCreateWithEdges(size_t, Edge *); + +/** + * Free all memory associated with the given graph. Since graphs are + * just a collection of numbers, they do not depend on each other in + * any way. + */ +extern void GraphFree(Graph *); + +/** + * Get the weight of the edge connecting the node specified first to + * the node specified second. If this is a directed graph, it does not + * necessarily follow that there is an edge from the node specified + * second to the node specified first. It also does not follow that + * such an edge, if it exists, has the same weight. + * .P + * This function will return -1 if the graph is invalid or either node + * is out of bounds. It will return 0 if there is no such edge from the + * node specified first to the node specified second. + */ +extern Edge GraphEdgeGet(Graph *, Node, Node); + +/** + * Set the weight of the edge connecting the node specified first to + * the node specified second. If this is not a directed graph, this + * function will have to be called twice, the second time reversing the + * order of the nodes. To remove the edge, specify a weight of 0. + */ +extern Edge GraphEdgeSet(Graph *, Node, Node, Edge); + +/** + * Get the number of nodes in the given graph. This operation is a + * simple memory access that happens in O(1) time. + */ +extern size_t GraphCountNodes(Graph *); + +/** + * Perform a breadth-first search on the given graph, starting at the + * specified node. This function returns a list of nodes in the order + * they were touched. The size of the list is stored in the unsigned + * integer pointed to by the last argument. + * .P + * If an error occurs, NULL will be returned. Otherwise, the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node * GraphBreadthFirstSearch(Graph *, Node, size_t *); + +/** + * Perform a depth-first search on the given graph, starting at the + * specified node. This function returns a list of nodes in the order + * they were touched. The size of the list is stored in the unsigned + * integer pointed to by the last argument. + * .P + * If an error occurs, NULL will be returned. Otherwise the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node *GraphDepthFirstSearch(Graph *, Node, size_t *); + +/** + * Perform a topological sort on the given graph. This function returns + * a list of nodes in topological ordering, though note that this is + * probably not the only topological ordering that exists for the + * graph. The size of the list is stored in the unsigned integer + * pointed to by the last argument. It should always be the number of + * nodes in the graph, but is provided for consistency and convenience. + * .P + * If an error occurs, NULL will be returned. Otherwise the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node *GraphTopologicalSort(Graph *, size_t *); + +/** + * Transpose the given graph, returning a brand new graph that is the + * result of the transposition. + */ +extern Graph * GraphTranspose(Graph *); + +#endif /* CYTOPLASM_GRAPH_H */