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 = '2019-02-14'; # DATE
4             our $VERSION = '0.005'; # VERSION
5              
6 1     1   873 use 5.010001;
  1         9  
7 1     1   7 use strict;
  1         1  
  1         23  
8 1     1   5 use warnings;
  1         1  
  1         137  
9              
10 1     1   8 use Exporter qw(import);
  1         2  
  1         418  
11             our @EXPORT_OK = qw(toposort is_cyclic is_acyclic);
12              
13             sub _toposort {
14 24     24   41 my $graph = shift;
15              
16             # this is the Kahn algorithm, ref:
17             # https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
18              
19 24         36 my %in_degree;
20 24         76 for my $k (keys %$graph) {
21 47   100     158 $in_degree{$k} //= 0;
22 47         60 for (@{ $graph->{$k} }) { $in_degree{$_}++ }
  47         82  
  46         116  
23             }
24              
25             # collect nodes with no incoming edges (in_degree = 0)
26 24         40 my @S;
27 24 100       80 for (sort keys %in_degree) { unshift @S, $_ if $in_degree{$_} == 0 }
  57         169  
28              
29 24         35 my @L;
30 24         175 while (@S) {
31 46         235 my $n = pop @S;
32 46         82 push @L, $n;
33 46         57 for my $m (@{ $graph->{$n} }) {
  46         101  
34 35 100       74 if (--$in_degree{$m} == 0) {
35 27         139 unshift @S, $m;
36             }
37             }
38             }
39              
40 24 100       62 if (@L == keys(%$graph)) {
41 19 100       35 if (@_) {
42 1     1   31 no warnings 'uninitialized';
  1         4  
  1         638  
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 4         6 my %pos;
46 4         11 for (0..$#L) { $pos{$L[$_]} = $_+1 }
  15         27  
47             return (0, [
48 4   66     6 sort { ($pos{$a} || @L+1) <=> ($pos{$b} || @L+1) } @{$_[0]}
  21   66     92  
  4         143  
49             ]);
50             } else {
51 15         52 return (0, \@L);
52             }
53             } else {
54             # there is a cycle
55 5         22 return (1, \@L);
56             }
57             }
58              
59             sub toposort {
60 10     10 1 1187 my ($err, $res) = _toposort(@_);
61 10 100       33 die "Can't toposort(), graph is cyclic" if $err;
62 9         65 @$res;
63             }
64              
65             sub is_cyclic {
66 7     7 1 2848 my ($err, $res) = _toposort(@_);
67 7         29 $err;
68             }
69              
70             sub is_acyclic {
71 7     7 1 2225 my ($err, $res) = _toposort(@_);
72 7         35 !$err;
73             }
74              
75             1;
76             # ABSTRACT: Utilities related to graph data structure
77              
78             __END__