File Coverage

blib/lib/Data/Graph/Util.pm
Criterion Covered Total %
statement 49 49 100.0
branch 10 10 100.0
condition 6 8 75.0
subroutine 9 9 100.0
pod 3 3 100.0
total 77 79 97.4


line stmt bran cond sub pod time code
1             package Data::Graph::Util;
2              
3             our $DATE = '2017-07-04'; # DATE
4             our $VERSION = '0.003'; # VERSION
5              
6 1     1   467 use 5.010001;
  1         3  
7 1     1   4 use strict;
  1         2  
  1         15  
8 1     1   4 use warnings;
  1         2  
  1         21  
9              
10 1     1   7 use Exporter qw(import);
  1         2  
  1         194  
11             our @EXPORT_OK = qw(toposort is_cyclic is_acyclic);
12              
13             sub _toposort {
14 22     22   60 my $graph = shift;
15              
16             # this is the Kahn algorithm, ref:
17             # https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
18              
19 22         32 my %in_degree;
20 22         64 for my $k (keys %$graph) {
21 42   100     159 $in_degree{$k} //= 0;
22 42         53 for (@{ $graph->{$k} }) { $in_degree{$_}++ }
  42         77  
  40         78  
23             }
24              
25             # collect nodes with no incoming edges (in_degree = 0)
26 22         34 my @S;
27 22 100       43 for (keys %in_degree) { unshift @S, $_ if $in_degree{$_} == 0 }
  50         119  
28              
29 22         36 my @L;
30 22         49 while (@S) {
31 39         60 my $n = pop @S;
32 39         69 push @L, $n;
33 39         48 for my $m (@{ $graph->{$n} }) {
  39         85  
34 29 100       64 if (--$in_degree{$m} == 0) {
35 23         59 unshift @S, $m;
36             }
37             }
38             }
39              
40 22 100       49 if (@L == keys(%$graph)) {
41 17 100       35 if (@_) {
42 1     1   6 no warnings 'uninitialized';
  1         2  
  1         231  
43             # user specifies a list to be sorted according to @L. this is like
44             # Sort::ByExample but we implement it ourselves to avoid dependency.
45 2         4 my %pos;
46 2         5 for (0..$#L) { $pos{$L[$_]} = $_+1 }
  8         15  
47             return (0, [
48 2   66     4 sort { ($pos{$a} || @L+1) <=> ($pos{$b} || @L+1) } @{$_[0]}
  6   66     33  
  2         7  
49             ]);
50             } else {
51 15         54 return (0, \@L);
52             }
53             } else {
54             # there is a cycle
55 5         17 return (1, \@L);
56             }
57             }
58              
59             sub toposort {
60 8     8 1 658 my ($err, $res) = _toposort(@_);
61 8 100       26 die "Can't toposort(), graph is cyclic" if $err;
62 7         40 @$res;
63             }
64              
65             sub is_cyclic {
66 7     7 1 1982 my ($err, $res) = _toposort(@_);
67 7         30 $err;
68             }
69              
70             sub is_acyclic {
71 7     7 1 1600 my ($err, $res) = _toposort(@_);
72 7         28 !$err;
73             }
74              
75             1;
76             # ABSTRACT: Utilities related to graph data structure
77              
78             __END__