Add tsort(1)
Signed-off-by: Mattias Andrée <maandree@kth.se>
This commit is contained in:
parent
fad1d35357
commit
b44d4d8edd
1
README
1
README
|
@ -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
72
tsort.1
Normal 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
218
tsort.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user