File Coverage

blib/lib/Bio/Coordinate/Graph.pm
Criterion Covered Total %
statement 82 88 93.1
branch 25 40 62.5
condition 3 3 100.0
subroutine 8 9 88.8
pod 5 5 100.0
total 123 145 84.8


line stmt bran cond sub pod time code
1             package Bio::Coordinate::Graph;
2             our $AUTHORITY = 'cpan:BIOPERLML';
3             $Bio::Coordinate::Graph::VERSION = '1.007001';
4 2     2   218350 use utf8;
  2         21  
  2         12  
5 2     2   76 use strict;
  2         4  
  2         48  
6 2     2   19 use warnings;
  2         3  
  2         74  
7 2     2   10 use parent qw(Bio::Root::Root);
  2         4  
  2         12  
8              
9             # ABSTRACT: Finds shortest path between nodes in a graph.
10             # AUTHOR: Heikki Lehvaslaiho
11             # OWNER: Heikki Lehvaslaiho
12             # LICENSE: Perl_5
13              
14              
15              
16             sub new {
17 8     8 1 108 my($class,@args) = @_;
18 8         30 my $self = $class->SUPER::new(@args);
19              
20 8         70 my($graph, $hasharray) =
21             $self->_rearrange([qw(
22             GRAPH
23             HASHARRAY
24             )],
25             @args);
26              
27 8 50       47 $graph && $self->graph($graph);
28 8 50       18 $hasharray && $self->hasharray($hasharray);
29              
30 8         35 $self->{'_root'} = undef;
31              
32 8         26 return $self; # success - we hope!
33             }
34              
35              
36             sub graph {
37              
38 0     0 1 0 my ($self,$value) = @_;
39              
40 0 0       0 if ($value) {
41 0 0       0 $self->throw("Need a hash of hashes")
42             unless ref($value) eq 'HASH' ;
43 0         0 $self->{'_dag'} = $value;
44              
45             # empty the cache
46 0         0 $self->{'_root'} = undef;
47              
48             }
49              
50 0         0 return $self->{'_dag'};
51              
52             }
53              
54              
55             sub hash_of_arrays {
56              
57 8     8 1 20 my ($self,$value) = @_;
58              
59             # empty the cache
60 8         11 $self->{'_root'} = undef;
61              
62 8 50       21 if ($value) {
63              
64 8 50       23 $self->throw("Need a hash of hashes")
65             unless ref($value) eq 'HASH' ;
66              
67             #copy the hash of arrays into a hash of hashes;
68 8         10 my %hash;
69 8         7 foreach my $start ( keys %{$value}){
  8         30  
70 79         73 $hash{$start} = undef;
71 79         55 map { $hash{$start}{$_} = 1 } @{$value->{$start}};
  93         138  
  79         94  
72             }
73              
74 8         18 $self->{'_dag'} = \%hash;
75             }
76              
77 8         20 return $self->{'_dag'};
78              
79             }
80              
81              
82             sub shortest_path {
83 21     21 1 27 my ($self, $root, $end) = @_;
84              
85 21 50       46 $self->throw("Two arguments needed") unless @_ == 3;
86             $self->throw("No node name [$root]")
87 21 50       45 unless exists $self->{'_dag'}->{$root};
88             $self->throw("No node name [$end]")
89 21 50       42 unless exists $self->{'_dag'}->{$end};
90              
91 21         18 my @res; # results
92             my $reverse;
93              
94 21 100       50 if ($root > $end) {
95 1         3 ($root, $end) = ($end, $root );
96 1         2 $reverse++;
97             }
98              
99             # try to use cached paths
100             $self->dijkstra($root) unless
101 21 100 100     98 defined $self->{'_root'} and $self->{'_root'} eq $root;
102              
103 21 50       63 return @res unless $self->{'_paths'} ;
104              
105             # create the list
106 21         27 my $node = $end;
107 21         27 my $prev = $self->{'_paths'}->{$end}{'prev'};
108 21         39 while ($prev) {
109 29         42 unshift @res, $node;
110 29         33 $node = $self->{'_paths'}->{$node}{'prev'};
111 29         67 $prev = $self->{'_paths'}->{$node}{'prev'};
112             }
113 21         25 unshift @res, $node;
114              
115 21 100       99 $reverse ? return reverse @res : return @res;
116             }
117              
118              
119             sub dijkstra {
120 14     14 1 16 my ($self,$root) = @_;
121              
122 14 50       24 $self->throw("I need the name of the root node input") unless $root;
123             $self->throw("No node name [$root]")
124 14 50       26 unless exists $self->{'_dag'}->{$root};
125              
126 14         23 my %est = (); # estimate hash
127 14         16 my %res = (); # result hash
128 14         11 my $nodes = keys %{$self->{'_dag'}};
  14         32  
129 14         16 my $maxdist = 1000000;
130              
131             # cache the root value
132 14         20 $self->{'_root'} = $root;
133              
134 14         12 foreach my $node ( keys %{$self->{'_dag'}} ){
  14         46  
135 136 100       159 if ($node eq $root) {
136 14         21 $est{$node}{'prev'} = undef;
137 14         21 $est{$node}{'dist'} = 0;
138             } else {
139 122         174 $est{$node}{'prev'} = undef;
140 122         134 $est{$node}{'dist'} = $maxdist;
141             }
142             }
143              
144             # remove nodes from %est until it is empty
145 14         41 while (keys %est) {
146              
147             #select the node closest to current one, or root node
148 111         77 my $min_node;
149 111         87 my $min = $maxdist;
150 111         270 foreach my $node (reverse sort keys %est) {
151 638 100       934 if ( $est{$node}{'dist'} < $min ) {
152 135         102 $min = $est{$node}{'dist'};
153 135         127 $min_node = $node;
154             }
155             }
156              
157             # no more links between nodes
158 111 100       186 last unless ($min_node);
159              
160             # move the node from %est into %res;
161 103         130 $res{$min_node} = delete $est{$min_node};
162              
163             # recompute distances to the neighbours
164 103         94 my $dist = $res{$min_node}{'dist'};
165 103         68 foreach my $neighbour ( keys %{$self->{'_dag'}->{$min_node}} ){
  103         241  
166 115 100       184 next unless $est{$neighbour}; # might not be there any more
167 89         84 $est{$neighbour}{'prev'} = $min_node;
168             $est{$neighbour}{'dist'} =
169             $dist + $self->{'_dag'}{$min_node}{$neighbour}
170 89 50       255 if $est{$neighbour}{'dist'} > $dist + 1 ;
171             }
172             }
173 14         41 return $self->{'_paths'} = \%res;
174             }
175              
176             1;
177              
178             __END__