[dev] [sbase][PATCH] Add tsort(1)

From: Mattias Andrée <maandree_AT_kth.se>
Date: Wed, 17 Feb 2016 01:17:33 +0100

Signed-off-by: Mattias Andrée <maandree_AT_kth.se>
---
 Makefile |   1 +
 README   |   1 +
 tsort.1  |  72 +++++++++++++++++++++
 tsort.c  | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 292 insertions(+)
 create mode 100644 tsort.1
 create mode 100644 tsort.c
diff --git a/Makefile b/Makefile
index 57be12a..754f4d2 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -150,6 +150,7 @@ BIN =\
 	touch\
 	tr\
 	true\
+	tsort\
 	tty\
 	uname\
 	unexpand\
diff --git a/README b/README
index 68a921c..ce41d19 100644
--- a/README
+++ b/README
_AT_@ -89,6 +89,7 @@ The following tools are implemented:
 =*|o touch       .
 #*|o tr          .
 =*|o true        .
+=* o tsort       .
 =*|o tty         .
 =*|o uname       .
 #*|o unexpand    .
diff --git a/tsort.1 b/tsort.1
new file mode 100644
index 0000000..27ec4f2
--- /dev/null
+++ b/tsort.1
_AT_@ -0,0 +1,72 @@
+.Dd 2016-02-16
+.Dt TSORT 1
+.Os sbase
+.Sh NAME
+.Nm tsort
+.Nd topological sort
+.Sh SYNOPSIS
+.Nm
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+topologically sorts a graph. The graph is read
+either from
+.Ar file
+or from standard input. The result is not optimized
+for any particular usage. Loops are detected and
+reported to standard error, but does not stop the sort.
+.Pp
+The input is a list of edges (vertex pairs), where
+the edge is directed from the first vertex to the
+second vertex.
+.Sh OPTIONS
+None.
+.Sh EXIT STATUS
+.Bl -tag -width Ds
+.It 0
+The graph as successfully sorted.
+.It 1
+The graph as successfully sorted, but contained loops.
+.It > 1
+An error occurred.
+.El
+.Sh EXAMPLES
+The input
+
+    a a
+    a b
+    a c
+    a c
+    a d
+    b c
+    c b
+    e f
+
+or equivalently
+
+    a a a b a c a c a d
+    b c c b e f
+
+represents the graph
+
+              ┌─┐
+              ↓ │
+             ┏━━━┓
+      ┌──────┃ a ┃──────┐
+      │      ┗━━━┛      │
+      │       │ │       │
+      ↓       ↓ ↓       ↓
+    ┏━━━┓───→┏━━━┓    ┏━━━┓
+    ┃ b ┃    ┃ c ┃    ┃ d ┃
+    ┗━━━┛←───┗━━━┛    ┗━━━┛
+
+    ┏━━━┓    ┏━━━┓
+    ┃ e ┃───→┃ f ┃
+    ┗━━━┛    ┗━━━┛
+
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2013
+specification.
diff --git a/tsort.c b/tsort.c
new file mode 100644
index 0000000..e193718
--- /dev/null
+++ b/tsort.c
_AT_@ -0,0 +1,218 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "util.h"
+
+#define eprintf(...)  enprintf(2, __VA_ARGS__)
+#define estrdup(...)  enstrdup(2, __VA_ARGS__)
+#define ecalloc(...)  encalloc(2, __VA_ARGS__)
+#define efshut(...)   enfshut(2, __VA_ARGS__)
+
+#define WHITE  0
+#define GREY   1
+#define BLACK  2
+
+struct vertex;
+
+struct edge
+{
+	struct vertex *to;
+	struct edge *next;
+};
+
+struct vertex
+{
+	char *name;
+	struct vertex *next;
+	struct edge edges;
+	size_t in_edges;
+	int colour;
+};
+
+static struct vertex graph;
+
+static void
+find_vertex(const char *name, struct vertex **it, struct vertex **prev)
+{
+	for (*prev = &graph; (*it = (*prev)->next); *prev = *it) {
+		int cmp = strcmp(name, (*it)->name);
+		if (cmp > 0)
+			continue;
+		if (cmp < 0)
+			*it = 0;
+		return;
+	}
+}
+
+static void
+find_edge(struct vertex* from, const char *to, struct edge **it, struct edge **prev)
+{
+	for (*prev = &(from->edges); (*it = (*prev)->next); *prev = *it) {
+		int cmp = strcmp(to, (*it)->to->name);
+		if (cmp > 0)
+			continue;
+		if (cmp < 0)
+			*it = 0;
+		return;
+	}
+}
+
+static struct vertex *
+add_vertex(char *name)
+{
+	struct vertex *vertex;
+	struct vertex *prev;
+
+	find_vertex(name, &vertex, &prev);
+	if (vertex)
+		return vertex;
+
+	vertex = ecalloc(1, sizeof(*vertex));
+	vertex->name = name;
+	vertex->next = prev->next;
+	prev->next = vertex;
+
+	return vertex;
+}
+
+static struct edge *
+add_edge(struct vertex* from, struct vertex* to)
+{
+	struct edge *edge;
+	struct edge *prev;
+
+	find_edge(from, to->name, &edge, &prev);
+	if (edge)
+		return edge;
+
+	edge = ecalloc(1, sizeof(*edge));
+	edge->to = to;
+	edge->next = prev->next;
+	prev->next = edge;
+	to->in_edges += 1;
+
+	return edge;
+}
+
+static void
+load_graph(FILE *fp)
+{
+#define SKIP(VAR, START, FUNC)  for (VAR = START; FUNC(*VAR) && *VAR; VAR++)
+#define TOKEN_END(P)            do { if (*P) *P++ = 0; else P = 0; } while (0)
+
+	char *line = 0;
+	size_t size = 0;
+	ssize_t len;
+	char *p;
+	char *name;
+	struct vertex *from = 0;
+
+	while ((len = getline(&line, &size, fp)) != -1) {
+		if (len && line[len - 1] == '\n')
+			line[len - 1] = 0;
+		for (p = line; p;) {
+			SKIP(name, p, isspace);
+			if (!*name)
+				break;
+			SKIP(p, name, !isspace);
+			TOKEN_END(p);
+			if (!from) {
+				from = add_vertex(estrdup(name));
+			} else if (strcmp(from->name, name)) {
+				add_edge(from, add_vertex(estrdup(name)));
+				from = 0;
+			} else {
+				from = 0;
+			}
+		}
+	}
+
+	free(line);
+
+	if (from)
+		eprintf("odd number of tokens in input, did you intended to use -l?\n");
+}
+
+static int
+sort_graph_visit(struct vertex *u)
+{
+	struct edge *e = &(u->edges);
+	struct vertex *v;
+	int r = 0;
+	u->colour = GREY;
+	printf("%s\n", u->name);
+	while ((e = e->next)) {
+		v = e->to;
+		if (v->colour == WHITE) {
+			v->in_edges -= 1;
+			if (v->in_edges == 0)
+				r |= sort_graph_visit(v);
+		} else if (v->colour == GREY) {
+			r = 1;
+			fprintf(stderr, "%s: loop detected between %s and %s\n",
+			        argv0, u->name, v->name);
+		} 
+	}
+	u->colour = BLACK;
+	return r;
+}
+
+static int
+sort_graph()
+{
+	struct vertex *u, *prev;
+	int r = 0;
+	size_t in_edges;
+	for (in_edges = 0; graph.next; in_edges++) {
+		for (prev = &graph; (u = prev->next); prev = u) {
+			if (u->colour != WHITE)
+				goto unlist;
+			if (u->in_edges > in_edges)
+				continue;
+			r |= sort_graph_visit(u);
+		unlist:
+			prev->next = u->next;
+			u = prev;
+		}
+	}
+	return r;
+}
+
+static void
+usage(void)
+{
+	eprintf("usage: %s [file]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+	FILE *fp = stdin;
+	const char *fn = "<stdin>";
+	int ret = 0;
+
+	ARGBEGIN {
+	default:
+		usage();
+	} ARGEND;
+
+	if (argc > 1)
+		usage();
+	if (argc && strcmp(*argv, "-"))
+		if (!(fp = fopen(fn = *argv, "r")))
+			eprintf("fopen %s:", *argv);
+
+	memset(&graph, 0, sizeof(graph));
+	load_graph(fp);
+	efshut(fp, fn);
+
+	ret = sort_graph();
+
+	if (fshut(stdout, "<stdout>") | fshut(stderr, "<stderr>"))
+		ret = 2;
+
+	return ret;
+}
-- 
2.7.1
Received on Wed Feb 17 2016 - 01:17:33 CET

This archive was generated by hypermail 2.3.0 : Wed Feb 17 2016 - 01:24:11 CET