File Coverage

blib/lib/Graph/Fast.pm
Criterion Covered Total %
statement 103 120 85.8
branch 14 24 58.3
condition 11 14 78.5
subroutine 19 20 95.0
pod 8 12 66.6
total 155 190 81.5


line stmt bran cond sub pod time code
1             package Graph::Fast;
2              
3 6     6   133992 use strict;
  6         16  
  6         224  
4 6     6   31 use warnings;
  6         13  
  6         163  
5 6     6   155 use 5.010;
  6         20  
  6         353  
6              
7             our $VERSION = "0.02";
8              
9 6     6   7533 use Data::Dumper;
  6         69029  
  6         473  
10 6     6   8852 use Storable qw(dclone);
  6         25379  
  6         495  
11 6     6   55 use List::Util qw(min);
  6         13  
  6         700  
12              
13 6     6   5178 use Hash::PriorityQueue;
  6         3823  
  6         9984  
14              
15             sub new {
16 5     5 1 89 my ($class, %args) = @_;
17 5 50   7   46 my $queue_maker = exists($args{queue_maker}) ? $args{queue_maker} : sub { Hash::PriorityQueue->new() };
  7         30  
18 5         44 return bless({
19             vertices => {},
20             edges => [],
21             _queue_maker => $queue_maker,
22             }, $class);
23             }
24              
25             sub count_edges {
26 21     21 1 55 my ($self) = @_;
27 21         30 return scalar(@{$self->{edges}});
  21         128  
28             }
29              
30             sub count_vertices {
31 21     21 1 43 my ($self) = @_;
32 21         31 return scalar(keys(%{$self->{vertices}}));
  21         105  
33             }
34              
35             sub add_vertex {
36 15     15 1 27 my ($self, $name) = @_;
37              
38 15 50       47 if (!exists($self->{vertices}->{$name})) {
39 15         83 $self->{vertices}->{$name} = { name => $name, edges_in => {}, edges_out => {} };
40             }
41 15         98 return $self->{vertices}->{$name};
42             }
43              
44             sub del_vertex {
45 6     6 1 14 my ($self, $name) = @_;
46              
47 6 50       28 if (exists($self->{vertices}->{$name})) {
48 6 100       10 @{$self->{edges}} = grep { $_->{from} ne $name and $_->{to} ne $name } @{$self->{edges}};
  6         20  
  23         140  
  6         18  
49 6         9 foreach my $in_edge (keys %{$self->{vertices}->{$name}->{edges_in}}) {
  6         27  
50 8         27 delete($self->{vertices}->{$in_edge}->{edges_out}->{$name});
51             }
52 6         12 foreach my $out_edge (keys %{$self->{vertices}->{$name}->{edges_out}}) {
  6         38  
53 4         11 delete($self->{vertices}->{$out_edge}->{edges_in}->{$name});
54             }
55 6         38 delete($self->{vertices}->{$name});
56             }
57             }
58              
59             sub dijkstra_worker {
60 9     9 0 15 my ($self, $from, $to) = @_;
61              
62 9         15 my $vert = $self->{vertices};
63 9         21 my $suboptimal = $self->{_queue_maker}->();
64 9         60 $suboptimal->insert($_, $self->{d_suboptimal}->{$_}) foreach (keys(%{$self->{d_suboptimal}}));
  9         49  
65 9         124 $self->{d_dist}->{$_} = -1 foreach (@{$self->{d_unvisited}});
  9         69  
66 9         20 $self->{d_dist}->{$from} = 0;
67              
68 9         10 while (1) {
69             # find the smallest unvisited node
70 31   100     218 my $current = $suboptimal->pop() // last;
71              
72             # update all neighbors
73 22         455 foreach my $edge (values %{$vert->{$current}->{edges_out}}) {
  22         67  
74 19 100 100     186 if (($self->{d_dist}->{$edge->{to}} == -1) ||
75             ($self->{d_dist}->{$edge->{to}} > ($self->{d_dist}->{$current} + $edge->{weight}) )) {
76 16         65 $suboptimal->update(
77             $edge->{to},
78             $self->{d_dist}->{$edge->{to}} = $self->{d_dist}->{$current} + $edge->{weight}
79             );
80             }
81             }
82             }
83              
84             # trace the path from the destination to the start
85 9         54 my @path = ();
86 9         12 my $current = $to;
87 9         20 NODE: while ($current ne $from) {
88 11         12 foreach my $edge (values %{$vert->{$current}->{edges_in}}) {
  11         32  
89 14 100       49 if ($self->{d_dist}->{$current} == $self->{d_dist}->{$edge->{from}} + $edge->{weight}) {
90 7         10 $current = $edge->{from};
91 7         9 unshift(@path, $edge);
92 7         39 next NODE;
93             }
94             }
95             # getting here means we found no predecessor - there is none.
96             # so there's no path.
97 4         36 return ();
98             }
99              
100 5         71 return @path;
101             }
102              
103             sub dijkstra_first {
104 9     9 0 15 my ($self, $from, $to) = @_;
105 9         13 $self->{d_from} = $from;
106 9         19 $self->{d_dist} = {};
107 9         35 $self->{d_unvisited} = [ grep { $_ ne $from } keys(%{$self->{vertices}}) ];
  42         92  
  9         29  
108 9         30 $self->{d_suboptimal} = { $from => 0 };
109              
110 9         52 dijkstra_worker($self, $from, $to);
111             }
112              
113             sub dijkstra_continue {
114 5     5 0 10 my ($self, $from, $to, $del_to) = @_;
115             # instead of reinitializing, it should invoke the worker after initializing
116             # to a state that assumes that an edge to $del_to has just been deleted.
117 5         12 goto &dijkstra_first;
118             }
119              
120             sub dijkstra {
121 9     9 1 1012 my ($self, $from, $to, $del_to) = @_;
122 9 100 100     49 if (!defined($self->{d_from}) or $self->{d_from} ne $from) {
123 4         15 goto &dijkstra_first;
124             } else {
125 5         16 goto &dijkstra_continue;
126             }
127             }
128              
129             sub recursive_dijkstra {
130 0     0 0 0 my ($self, $from, $to, $level, $del_to) = @_;
131 0         0 my @d = ([ $self->dijkstra($from, $to, $del_to) ]);
132              
133 0 0       0 if (!defined($d[0]->[0])) {
134 0         0 return ();
135             }
136              
137 0 0       0 if ($level > 0) {
138 0         0 foreach (0..(@{$d[0]}-1)) {
  0         0  
139             # from copies of the graph, remove one edge from the result path,
140             # and continue finding paths on that tree.
141 0         0 my $ffffuuuu = $self->{_queue_maker};
142 0         0 $self->{_queue_maker} = "omg";
143 0         0 my $g2 = dclone($self);
144 0         0 $g2->{_queue_maker} = $self->{_queue_maker} = $ffffuuuu;
145              
146 0         0 $g2->del_edge($d[0]->[$_]->{from}, $d[0]->[$_]->{to});
147 0         0 my @new = $g2->recursive_dijkstra($from, $to, $level - 1, $d[0]->[$_]->{to});
148              
149             # add all new paths, unless they are already present in the result set
150 0         0 foreach my $n (@new) {
151 0 0       0 push(@d, $n) unless (grep { $n ~~ $_ } @d);
  0         0  
152             }
153             }
154             }
155              
156 0         0 @d;
157             }
158              
159             sub add_edge {
160 31     31 1 93 my ($self, $from, $to, $weight, $user_data) = @_;
161 31         76 $self->del_edge($from => $to);
162              
163 31 50       211 my $edge = { from => $from, to => $to, weight => $weight, (defined($user_data) ? (user_data => $user_data) : ()) };
164              
165 31         43 push(@{$self->{edges}}, $edge);
  31         59  
166 31   33     124 ($self->{vertices}->{$from} // $self->add_vertex($from))->{edges_out}->{$to} = $edge;
167 31   66     149 ($self->{vertices}->{$to } // $self->add_vertex($to ))->{edges_in }->{$from} = $edge;
168             }
169              
170             sub del_edge {
171 33     33 1 53 my ($self, $from, $to) = @_;
172              
173             # find the edge. assume it only exists once -> only delete the first.
174             # while we're at it, delete the edge from the source vertex...
175 33         102 my $e = $self->{vertices}->{$from}->{edges_out}->{$to};
176 33 100       97 return undef if (!defined($e));
177 2         6 delete($self->{vertices}->{$from}->{edges_out}->{$to});
178              
179             # now search it in the destination vertex' list, delete it there
180             # also only delete the first matching one here (though now there
181             # shouldn't be any duplicates at all because now we're matching the
182             # actual edge, not just its endpoints like above.
183 2         4 delete($self->{vertices}->{$to}->{edges_in}->{$from});
184              
185             # and remove it from the graph's vertex list
186 2         5 @{$self->{edges}} = grep { $_ != $e } @{$self->{edges}}
  2         8  
  3         8  
  2         5  
187             }
188              
189             1;
190              
191             __END__