Add tsort(1)

Signed-off-by: Mattias Andrée <maandree@kth.se>
This commit is contained in:
Mattias Andrée 2016-02-17 01:17:33 +01:00 committed by sin
parent fad1d35357
commit b44d4d8edd
4 changed files with 292 additions and 0 deletions

View File

@ -150,6 +150,7 @@ BIN =\
touch\
tr\
true\
tsort\
tty\
uname\
unexpand\

1
README
View File

@ -89,6 +89,7 @@ The following tools are implemented:
=*|o touch .
#*|o tr .
=*|o true .
=* o tsort .
=*|o tty .
=*|o uname .
#*|o unexpand .

72
tsort.1 Normal file
View File

@ -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.

218
tsort.c Normal file
View File

@ -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(void)
{
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;
}