1 /* tsort - topological sort.
2 Copyright (C) 1998-2005 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18 /* Written by Mark Kettenis <kettenis@phys.uva.nl>. */
20 /* The topological sort is done according to Algorithm T (Topological
21 sort) in Donald E. Knuth, The Art of Computer Programming, Volume
22 1/Fundamental Algorithms, page 262. */
29 #include <sys/types.h>
32 #include "long-options.h"
35 #include "readtokens.h"
37 /* The official name of this program (e.g., no `g' prefix). */
38 #define PROGRAM_NAME "tsort"
40 #define AUTHORS "Mark Kettenis"
42 /* Token delimiters when reading from a file. */
45 /* Members of the list of successors. */
49 struct successor
*next
;
52 /* Each string is held in core as the head of a list of successors. */
56 struct item
*left
, *right
;
57 int balance
; /* -1, 0, or +1 */
60 struct successor
*top
;
63 /* The name this program was run with. */
66 /* The head of the sorted list. */
67 static struct item
*head
= NULL
;
69 /* The tail of the list of `zeros', strings that have no predecessors. */
70 static struct item
*zeros
= NULL
;
72 /* Used for loop detection. */
73 static struct item
*loop
= NULL
;
75 /* The number of strings to sort. */
76 static size_t n_strings
= 0;
81 if (status
!= EXIT_SUCCESS
)
82 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
87 Usage: %s [OPTION] [FILE]\n\
88 Write totally ordered list consistent with the partial ordering in FILE.\n\
89 With no FILE, or when FILE is -, read standard input.\n\
92 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
93 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
94 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT
);
100 /* Create a new item/node for STR. */
102 new_item (const char *str
)
104 struct item
*k
= xmalloc (sizeof *k
);
106 k
->str
= (str
? xstrdup (str
): NULL
);
107 k
->left
= k
->right
= NULL
;
110 /* T1. Initialize (COUNT[k] <- 0 and TOP[k] <- ^). */
118 /* Search binary tree rooted at *ROOT for STR. Allocate a new tree if
119 *ROOT is NULL. Insert a node/item for STR if not found. Return
120 the node/item found/created for STR.
122 This is done according to Algorithm A (Balanced tree search and
123 insertion) in Donald E. Knuth, The Art of Computer Programming,
124 Volume 3/Searching and Sorting, pages 455--457. */
127 search_item (struct item
*root
, const char *str
)
129 struct item
*p
, *q
, *r
, *s
, *t
;
134 /* Make sure the tree is not empty, since that is what the algorithm
136 if (root
->right
== NULL
)
137 return (root
->right
= new_item (str
));
139 /* A1. Initialize. */
146 a
= strcmp (str
, p
->str
);
150 /* A3 & A4. Move left & right. */
161 /* A3 & A4. (continued). */
167 /* A6. Adjust balance factors. */
168 assert (!STREQ (str
, s
->str
));
169 if (strcmp (str
, s
->str
) < 0)
182 assert (!STREQ (str
, p
->str
));
183 if (strcmp (str
, p
->str
) < 0)
195 /* A7. Balancing act. */
196 if (s
->balance
== 0 || s
->balance
== -a
)
204 /* A8. Single Rotation. */
216 s
->balance
= r
->balance
= 0;
220 /* A9. Double rotation. */
242 else if (p
->balance
== -a
)
247 /* A10. Finishing touch. */
256 /* A3 & A4. (continued). */
269 /* Record the fact that J precedes K. */
272 record_relation (struct item
*j
, struct item
*k
)
276 if (!STREQ (j
->str
, k
->str
))
279 p
= xmalloc (sizeof *p
);
287 count_items (struct item
*unused ATTRIBUTE_UNUSED
)
294 scan_zeros (struct item
*k
)
296 /* Ignore strings that have already been printed. */
297 if (k
->count
== 0 && k
->str
)
310 /* Try to detect the loop. If we have detected that K is part of a
311 loop, print the loop on standard error, remove a relation to break
312 the loop, and return true.
314 The loop detection strategy is as follows: Realise that what we're
315 dealing with is essentially a directed graph. If we find an item
316 that is part of a graph that contains a cycle we traverse the graph
317 in backwards direction. In general there is no unique way to do
318 this, but that is no problem. If we encounter an item that we have
319 encountered before, we know that we've found a cycle. All we have
320 to do now is retrace our steps, printing out the items until we
321 encounter that item again. (This is not necessarily the item that
322 we started from originally.) Since the order in which the items
323 are stored in the tree is not related to the specified partial
324 ordering, we may need to walk the tree several times before the
325 loop has completely been constructed. If the loop was found, the
326 global variable LOOP will be NULL. */
329 detect_loop (struct item
*k
)
333 /* K does not have to be part of a cycle. It is however part of
334 a graph that contains a cycle. */
337 /* Start traversing the graph at K. */
341 struct successor
**p
= &k
->top
;
345 if ((*p
)->suc
== loop
)
349 /* We have found a loop. Retrace our steps. */
352 struct item
*tmp
= loop
->qlink
;
354 fprintf (stderr
, "%s: %s\n", program_name
,
357 /* Until we encounter K again. */
360 /* Remove relation. */
366 /* Tidy things up since we might have to
367 detect another loop. */
374 struct item
*tmp
= loop
->qlink
;
380 /* Since we have found the loop, stop walking
400 /* Recurse (sub)tree rooted at ROOT, calling ACTION for each node.
401 Stop when ACTION returns true. */
404 recurse_tree (struct item
*root
, bool (*action
) (struct item
*))
406 if (root
->left
== NULL
&& root
->right
== NULL
)
407 return (*action
) (root
);
410 if (root
->left
!= NULL
)
411 if (recurse_tree (root
->left
, action
))
413 if ((*action
) (root
))
415 if (root
->right
!= NULL
)
416 if (recurse_tree (root
->right
, action
))
423 /* Walk the tree specified by the head ROOT, calling ACTION for
427 walk_tree (struct item
*root
, bool (*action
) (struct item
*))
430 recurse_tree (root
->right
, action
);
433 /* Do a topological sort on FILE. Return true if successful. */
436 tsort (const char *file
)
440 struct item
*j
= NULL
;
441 struct item
*k
= NULL
;
442 token_buffer tokenbuffer
;
443 bool is_stdin
= STREQ (file
, "-");
445 /* Intialize the head of the tree will hold the strings we're sorting. */
446 root
= new_item (NULL
);
448 if (!is_stdin
&& ! freopen (file
, "r", stdin
))
449 error (EXIT_FAILURE
, errno
, "%s", file
);
451 init_tokenbuffer (&tokenbuffer
);
455 /* T2. Next Relation. */
456 size_t len
= readtoken (stdin
, DELIM
, sizeof (DELIM
) - 1, &tokenbuffer
);
457 if (len
== (size_t) -1)
462 k
= search_item (root
, tokenbuffer
.buffer
);
465 /* T3. Record the relation. */
466 record_relation (j
, k
);
474 error (EXIT_FAILURE
, 0, _("%s: input contains an odd number of tokens"),
477 /* T1. Initialize (N <- n). */
478 walk_tree (root
, count_items
);
480 while (n_strings
> 0)
482 /* T4. Scan for zeros. */
483 walk_tree (root
, scan_zeros
);
487 struct successor
*p
= head
->top
;
489 /* T5. Output front of queue. */
490 printf ("%s\n", head
->str
);
491 head
->str
= NULL
; /* Avoid printing the same string twice. */
494 /* T6. Erase relations. */
498 if (p
->suc
->count
== 0)
500 zeros
->qlink
= p
->suc
;
507 /* T7. Remove from queue. */
511 /* T8. End of process. */
514 /* The input contains a loop. */
515 error (0, 0, _("%s: input contains a loop:"), file
);
518 /* Print the loop and remove a relation to break it. */
520 walk_tree (root
, detect_loop
);
525 if (fclose (stdin
) != 0)
526 error (EXIT_FAILURE
, errno
, "%s",
527 is_stdin
? _("standard input") : quote (file
));
533 main (int argc
, char **argv
)
537 initialize_main (&argc
, &argv
);
538 program_name
= argv
[0];
539 setlocale (LC_ALL
, "");
540 bindtextdomain (PACKAGE
, LOCALEDIR
);
541 textdomain (PACKAGE
);
543 atexit (close_stdout
);
545 parse_long_options (argc
, argv
, PROGRAM_NAME
, PACKAGE
, VERSION
,
546 usage
, AUTHORS
, (char const *) NULL
);
547 if (getopt_long (argc
, argv
, "", NULL
, NULL
) != -1)
548 usage (EXIT_FAILURE
);
550 if (1 < argc
- optind
)
552 error (0, 0, _("extra operand %s"), quote (argv
[optind
+ 1]));
553 usage (EXIT_FAILURE
);
556 ok
= tsort (optind
== argc
? "-" : argv
[optind
]);
558 exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);