File Coverage

blib/lib/Sort/Topological.pm
Criterion Covered Total %
statement 9 66 13.6
branch 0 14 0.0
condition 0 3 0.0
subroutine 3 8 37.5
pod 0 4 0.0
total 12 95 12.6


line stmt bran cond sub pod time code
1             package Sort::Topological;
2              
3             #########################################################################
4              
5             =head1 NAME
6              
7             Sort::Topological - Topological Sort
8              
9             =head1 SYNOPSIS
10              
11             use Sort::Topological qw(toposort);
12             my @result = toposort($item_direct_sub, @items);
13              
14             =head1 DESCRIPTION
15              
16             Sort::Topological does a topological sort of an acyclical directed graph.
17              
18             =head1 EXAMPLE
19              
20             my %children = (
21             'a' => [ 'b', 'c' ],
22             'c' => [ 'x' ],
23             'b' => [ 'x' ],
24             'x' => [ 'y' ],
25             'y' => [ 'z' ],
26             'z' => [ ],
27             );
28             sub children { @{$children{$_[0]} || []}; }
29             my @unsorted = ( 'z', 'a', 'x', 'c', 'b', 'y' );
30             my @sorted = toposort(\&children, \@unsorted);
31              
32              
33             In the above example C<%children> is the graph, C<&children($x)> returns a list of targets of the directed graph from C<$x>.
34              
35             C<@sorted> is sorted such that:
36              
37             =over 4
38              
39             for any C<$x> in C<@sorted>:
40              
41             =over 4
42             C<$x> is not reachable through the directed graph of anything after C<$x> in C<@sorted>.
43              
44             =back
45              
46             i.e.: 'y' is not reachable by 'z', 'x' is not reachable by 'y' or 'z', and so on.
47              
48             =back
49            
50             =head1 CAVEATS
51              
52             =over 4
53              
54             =item *
55              
56             Does not handle cyclical graphs.
57              
58             =back
59              
60             =head1 STATUS
61              
62             If you find this to be useful please contact the author. This is alpha software; all APIs, semantics and behavors are subject to change.
63              
64             =head1 INTERFACE
65              
66             This section describes the external interface of this module.
67              
68              
69             =cut
70              
71              
72             #########################################################################
73              
74              
75 4     4   22 use strict;
  4         6  
  4         137  
76 4     4   19 use warnings;
  4         8  
  4         418  
77              
78             our $VERSION = '0.02';
79             our $REVISION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d." . "%02d" x $#r, @r };
80              
81             our $PACKAGE = __PACKAGE__;
82              
83 4     4   22 use Exporter;
  4         6  
  4         3565  
84             our @ISA = qw(Exporter);
85             our @EXPORT = qw();
86             our @EXPORT_OK = qw(toposort);
87             our %EXPORT_TAGS = (
88             'all' => \@EXPORT_OK,
89             );
90              
91              
92             sub toposort
93             {
94 0     0 0   my ($deps, $in) = @_;
95              
96             # Assign the depth of traversal.
97 0           my %depth;
98             {
99             # Assign a base depth of traversal for the input.
100 0           my @stack = reverse map([ $_, 1 ], @$in);
  0            
101              
102             # While there are still items to traverse,
103 0           while ( @stack ) {
104             # Pop the top item and the current traversal depth.
105 0           my $q = pop @stack;
106 0           my $x = $q->[0];
107 0           my $d = $q->[1];
108              
109             # Remember current depth.
110 0 0 0       if ( (! defined $depth{$x}) || $depth{$x} < $d ) {
111 0           $depth{$x} = $d;
112             # warn "$x depth = $d";
113             }
114              
115             # Push the next items along the graph, remembering the depth they were found at.
116 0           if ( 1 ) {
117 0           my @depa = $deps->($x);
118 0           unshift(@stack, reverse map([ $_, $d + 1 ], @depa));
119             }
120             }
121             }
122            
123             # print STDERR 'depth = ', join(', ', %depth), "\n";
124              
125             # Create a depth tie-breaker map based on order of appearance of list.
126 0           my %order;
127             {
128 0           my $i = 0;
  0            
129 0           %order = map(($_, ++ $i), @$in);
130             }
131              
132             # Sort by depth and input order.
133 0 0         my @out = sort {
134 0           $depth{$a} <=> $depth{$b} ||
135             $order{$a} <=> $order{$b}
136             } @$in;
137              
138             # Return array or array ref.
139 0 0         wantarray ? @out : \@out;
140             }
141              
142              
143             sub deep_deps
144             {
145 0     0 0   my ($deps, @x) = @_;
146            
147 0           my @out;
148            
149 0           @x = map($deps->($_), @x);
150            
151 0           while ( @x ) {
152 0           my $x = shift @x;
153 0           push(@out, $x);
154 0           push(@x, $deps->($x));
155             }
156            
157 0           @out;
158             }
159              
160              
161             sub validate_sorted
162             {
163 0     0 0   my ($dep, @sorted) = @_;
164 0           my $ok = 1;
165              
166 0           my @after = @sorted;
167 0           my @before;
168 0           while ( @after ) {
169 0           my $x = shift @after;
170 0           my @deep_deps = deep_deps($dep, @after);
171             # warn " @deep_deps";
172             # each $x is not a dep of anything after it.
173 0 0         if ( grep($_ eq $x, @deep_deps) ) {
174 0           warn "found $x in @deep_deps";
175 0           $ok = 0;
176             }
177 0           push(@before, $x);
178             }
179              
180             $ok
181 0           }
182              
183              
184             sub UNIT_TEST
185             {
186 0     0 0   print STDERR "VERSION = $VERSION, PACKAGE = $PACKAGE\n";
187 0           my %children = (
188             'a' => [ 'b', 'c' ],
189             'b' => [ 'd' ],
190             'c' => [ 'e', 'y' ],
191             'd' => [ 'x' ],
192             'e' => [ 'y', 'z' ],
193             'f' => [ 'z' ],
194             'x' => [ 'y' ],
195             'y' => [ 'z' ],
196             'z' => [ ],
197             );
198              
199 0           my $passes = 20;
200 0           my $verbose = 0;
201              
202 0           for my $pass ( 1 .. $passes ) {
203 0           my @unsorted = ( 'a', 'b', 'c', 'd', 'e', 'f', 'x', 'y', 'z' );
204 0           for my $i ( 0 .. $#unsorted ) {
205 0           my $j = rand($#unsorted);
206 0           ($unsorted[$i], $unsorted[$j]) = ($unsorted[$j], $unsorted[$i]);
207             }
208 0 0   0     my $children = sub { @{$children{$_[0]} || []} };
  0            
  0            
209            
210 0           $DB::single = 1;
211 0           my @sorted = toposort($children, \@unsorted);
212            
213 0 0         print 'unsorted = ', join(', ', @unsorted), "\n" if $verbose;
214 0 0         print ' sorted = ', join(', ', @sorted), "\n" if $verbose;
215 0           validate_sorted($children, @sorted);
216             }
217             }
218              
219              
220             # UNIT_TEST(@ARGV);
221              
222             #########################################################################
223              
224             =head1 VERSION
225              
226             Version 0.01, $Revision: 1.2 $.
227              
228             =head1 AUTHOR
229              
230             Kurt A. Stephens
231              
232             =head1 COPYRIGHT
233              
234             Copyright (c) 2001, 2002, Kurt A. Stephens and ION, INC.
235              
236             =head1 SEE ALSO
237              
238             >.
239              
240             =cut
241              
242             ##################################################
243              
244             1;
245              
246             ### Keep these comments at end of file: kurtstephens@acm.org 2001/12/28 ###
247             ### Local Variables: ###
248             ### mode:perl ###
249             ### perl-indent-level:2 ###
250             ### perl-continued-statement-offset:0 ###
251             ### perl-brace-offset:0 ###
252             ### perl-label-offset:0 ###
253             ### End: ###
254