File Coverage

blib/lib/Graph/Simple.pm
Criterion Covered Total %
statement 145 146 99.3
branch 32 32 100.0
condition 15 20 75.0
subroutine 22 22 100.0
pod 11 11 100.0
total 225 231 97.4


line stmt bran cond sub pod time code
1             package Graph::Simple;
2              
3             #ABSTRACT: simple and intuitive interface for manipulating graph
4              
5              
6 1     1   25054 use Moo;
  1         20751  
  1         7  
7 1     1   1993 use Carp 'croak';
  1         2  
  1         2324  
8              
9             # graphs are represented with adjency lists
10             has _adjencies => (
11             is => 'rw',
12             default => sub { {} },
13             );
14              
15             # if weights are provided, stored for each edge(u,v)
16             has _weights => (
17             is => 'rw',
18             default => sub { {} },
19             );
20              
21              
22             has is_weighted => (
23             is => 'ro',
24             default => sub {0},
25             );
26              
27              
28             has is_directed => (
29             is => 'ro',
30             default => sub {0},
31             );
32              
33              
34             sub vertices {
35 122     122 1 139 my $self = shift;
36 122         110 return keys %{ $self->_adjencies };
  122         471  
37             }
38              
39              
40             sub add_edge {
41 77     77 1 362 my ( $self, $u, $v, $weight ) = @_;
42              
43 77         138 $self->_add_edge( $u, $v, $weight );
44              
45             # if the graph is not directed, adding u,v adds v,u
46 77 100       236 $self->_add_edge( $v, $u, $weight ) if !$self->is_directed;
47              
48 77         172 return "$u,$v";
49             }
50              
51             sub _add_edge {
52 146     146   210 my ( $self, $u, $v, $weight ) = @_;
53 146   100     353 $weight ||= 0;
54              
55 146   100     529 $self->_adjencies->{$u} ||= [];
56 146         153 push @{ $self->_adjencies->{$u} }, $v;
  146         321  
57              
58 146 100       566 $self->_weights->{$u}->{$v} = $weight
59             if $self->is_weighted;
60             }
61              
62              
63             sub delete_edge {
64 2     2 1 6 my ( $self, $u, $v ) = @_;
65              
66 2         8 $self->_delete_edge( $u, $v );
67 2 100       19 $self->_delete_edge( $v, $u ) if !$self->is_directed;
68             }
69              
70             sub _delete_edge {
71 3     3   6 my ( $self, $u, $v ) = @_;
72              
73 3         9 my @neighbors = $self->neighbors($u);
74 3         7 my @new;
75 3         6 foreach my $e (@neighbors) {
76 4 100       18 push @new, $e if $e ne $v;
77             }
78 3         28 $self->_adjencies->{$u} = \@new;
79             }
80              
81              
82             sub neighbors {
83 116     116 1 175 my ( $self, $v ) = @_;
84              
85 804         3400 croak "Unknown vertex '$v'"
86 116 100       210 if !grep {/^$v$/} $self->vertices;
87              
88 115         186 return @{ $self->_adjencies->{$v} };
  115         479  
89             }
90              
91              
92             sub weight {
93 70     70 1 3926 my ( $self, $u, $v, $w ) = @_;
94 70 100       132 if ( @_ == 3 ) {
95 69         244 return $self->_weights->{$u}->{$v};
96             }
97             else {
98 1         7 $self->_weights->{$u}->{$v} = $w;
99             }
100             }
101              
102              
103             sub is_adjacent {
104 2     2 1 4108 my ( $self, $u, $v ) = @_;
105 2         7 return grep {/^$v$/} @{ $self->_adjencies->{$u} };
  6         79  
  2         16  
106             }
107              
108              
109             sub breadth_first_search {
110 1     1 1 9 my ( $self, $v, %options ) = @_;
111              
112 1         3 my @queue = ($v);
113 1         2 my $parents = {};
114 1         3 my $states = { $v => 'grey' };
115              
116 8     8   11 my $cb_vertex_discovered = $options{cb_vertex_discovered} || sub {
117 1   50     13 };
118              
119 8     8   23 my $cb_vertex_processed = $options{cb_vertex_processed} || sub {
120 1   50     9 };
121              
122 4     4   6 my $cb_edge_discovered = $options{cb_edge_discovered} || sub {
123 1   50     8 };
124              
125 1         5 while ( my $vertex = shift(@queue) ) {
126 8         18 $cb_vertex_discovered->($vertex);
127              
128 8         17 foreach my $n ( $self->neighbors($vertex) ) {
129 22   100     75 my $state = $states->{$n} || 'white';
130 22 100       53 next if $state eq 'black';
131              
132 11 100       19 if ( $state eq 'grey' ) {
133 4         16 $cb_edge_discovered->( $vertex, $n );
134 4         6 next;
135             }
136              
137 7         11 push @queue, $n;
138 7         14 $states->{$n} = 'grey';
139 7         15 $parents->{$n} = $vertex;
140             }
141              
142 8         21 $states->{$vertex} = 'black';
143 8         14 $cb_vertex_processed->($vertex);
144             }
145              
146 1         18 return $parents;
147             }
148              
149              
150             sub depth_first_search {
151 3     3 1 1634 my ( $self, $start, %options ) = @_;
152              
153             # init phase of the DFS traversal
154 3   50     17 my $states ||= {};
155 3   100 7   17 my $cb_vd = $options{cb_vertex_discovered} || sub { };
  7         9  
156 3   100 13   18 my $cb_vp = $options{cb_vertex_processed} || sub { };
  13         18  
157 3   50 38   23 my $cb_ed = $options{cb_edge_discovered} || sub { };
  38         42  
158 3         11 foreach my $v ( $self->vertices ) {
159 20         39 $states->{$v} = 'unknown';
160             }
161              
162             # DFS traversal is recursively done on each new vertex
163             $self->_dfs_visit(
164 3         22 $start, $states,
165             { cb_vertex_discovered => $cb_vd,
166             cb_vertex_processed => $cb_vp,
167             cb_edge_discovered => $cb_ed,
168             }
169             );
170             }
171              
172             sub _dfs_visit {
173 20     20   36 my ( $self, $vertex, $states, $callbacks ) = @_;
174              
175 20         34 $states->{$vertex} = 'discovered';
176 20         49 $callbacks->{cb_vertex_discovered}->($vertex);
177              
178 20         70 foreach my $n ( $self->neighbors($vertex) ) {
179              
180 38         80 $callbacks->{cb_edge_discovered}->( $vertex, $n );
181 38         54 my $state = $states->{$n};
182              
183 38 100       106 if ( $state eq 'unknown' ) {
184 17         44 $self->_dfs_visit( $n, $states, $callbacks );
185             }
186             }
187              
188 20         52 $callbacks->{cb_vertex_processed}->($vertex);
189 20         83 $states->{$vertex} = 'processed';
190             }
191              
192              
193             sub prim {
194 1     1 1 8 my ( $self, $start ) = @_;
195 1         30 my $spanning_tree =
196             Graph::Simple->new( is_weighted => 0, is_directed => 0 );
197              
198 1         11 my %non_tree_vertices = map { $_ => 1 } $self->vertices;
  6         13  
199 1         4 my %tree_vertices = ( $start => 1 );
200              
201 1         23 my $current = $start;
202 1         5 while ( keys %non_tree_vertices ) {
203 5         8 delete $non_tree_vertices{$current};
204              
205             # select the edge of minimum weight between a tree and a nontree vertex
206 5         6 my $min_weight;
207             my $new_edge;
208 5         11 foreach my $u ( keys %tree_vertices ) {
209              
210 15         34 foreach my $v ( $self->neighbors($u) ) {
211 42 100       97 next if exists $tree_vertices{$v};
212              
213 18         35 my $w = $self->weight( $u, $v );
214              
215             # print " - $u, $v weights $w\n";
216              
217 18 100       38 $min_weight = $w if !defined $min_weight;
218 18 100       52 if ( $w <= $min_weight ) {
219 11         35 $new_edge = [ $u, $v ];
220 11         27 $min_weight = $w;
221             }
222             }
223             }
224              
225             # Adding $v to the spanning tree
226 5         8 my ( $u, $v ) = @$new_edge;
227              
228             # print "Minimum vertex is $u -> $v\n";
229 5         12 $spanning_tree->add_edge( $u, $v );
230 5         8 delete $non_tree_vertices{$v};
231 5         19 $tree_vertices{$v} = 1;
232             }
233              
234 1         6 return $spanning_tree;
235             }
236              
237              
238             sub dijkstra {
239 2     2 1 9 my ( $self, $vertex ) = @_;
240 2         56 my $spanning_tree = Graph::Simple->new;
241              
242 2         13 my %distances = ( $vertex => 0 );
243 2         6 my %non_tree_vertices = map { $_ => 1 } $self->vertices;
  16         30  
244 2         8 my %tree_vertices = ( $vertex => 1 );
245              
246 2         4 my $current = $vertex;
247 2         9 while ( keys %non_tree_vertices ) {
248 14         18 delete $non_tree_vertices{$current};
249              
250             # select the edge of minimum weight between a tree and a nontree vertex
251 14         15 my $min_dist;
252             my $new_edge;
253 14         29 foreach my $u ( keys %tree_vertices ) {
254              
255 56         111 foreach my $v ( $self->neighbors($u) ) {
256 158 100       964 next if exists $tree_vertices{$v};
257              
258 42         72 my $w = $self->weight( $u, $v );
259 42         59 my $distance = $distances{$u} + $w;
260              
261 42 100       70 $min_dist = $distance if !defined $min_dist;
262 42 100       85 if ( $distance <= $min_dist ) {
263 29         52 $new_edge = [ $u, $v ];
264 29         34 $min_dist = $distance;
265 29         62 $distances{$v} = $distance;
266             }
267             }
268             }
269              
270             # Adding $v to the spanning tree
271 14         27 my ( $u, $v ) = @$new_edge;
272 14         24 $spanning_tree->add_edge( $u, $v );
273 14         22 delete $non_tree_vertices{$v};
274 14         45 $tree_vertices{$v} = 1;
275             }
276              
277 2         14 return { spanning_tree => $spanning_tree, distances => \%distances };
278             }
279              
280              
281             sub shortest_path {
282 1     1 1 3315 my ( $self, $source, $destination ) = @_;
283 1         6 my $dijkstra = $self->dijkstra($source);
284              
285 1         3 my $mst = $dijkstra->{spanning_tree};
286              
287             # we build a reverse path, starting from the destination, and backtracking the
288             # source each step with the neighbors of the vertex in the spanning tree
289 1         2 my @reverse_path;
290 1         2 my $current = $destination;
291              
292 1         4 while ( $current ne $source ) {
293 2         4 push @reverse_path, $current;
294              
295 2         6 foreach my $n ( $mst->neighbors($current) ) {
296 2 100       6 if ( $n eq $source ) {
297 1         3 push @reverse_path, $n;
298 1         16 return reverse @reverse_path;
299             }
300             else {
301 1         4 $current = $n;
302             }
303             }
304             }
305              
306 0           return reverse @reverse_path;
307             }
308              
309             1;
310              
311              
312             =pod
313              
314             =head1 NAME
315              
316             Graph::Simple - simple and intuitive interface for manipulating graph
317              
318             =head1 VERSION
319              
320             version 0.04
321              
322             =head1 DESCRIPTION
323              
324             In computer science, a graph is an abstract data type that is meant to implement
325             the graph and hypergraph concepts from mathematics.
326              
327             A graph data structure consists of a finite (and possibly mutable) set of
328             ordered pairs, called I, of certain entities called I.
329             As in mathematics, an edge (x,y) is said to point or go from x to y.
330              
331             A graph data structure may also associate to each edge some edge value, such as
332             a symbolic label or a numeric attribute (cost, capacity, length, etc.) most
333             oftenly refered to as the I of the edge.
334              
335             See L for
336             more details about the theory.
337              
338             This class provides an easy to use and intuitive API for manipulating graphs in
339             Perl. It's a native Perl implementation based on L.
340              
341             =head1 ATTRIBUTES
342              
343             =head2 is_weighted
344              
345             Boolean flag to tell if the graph is weighted
346              
347             =head2 is_directed
348              
349             Boolean flag to tell if the graph is directed
350              
351             =head1 METHODS
352              
353             =head2 vertices
354              
355             Return the array of vertices
356              
357             my @vertices = $g->vertices;
358              
359             =head2 add_edge
360              
361             Adds a new edge to the graph, and add the corresponding vertices.
362             Note that on undirected graphs, adding u,v also adds v,u.
363              
364             $g->add_edge("Foo", "Bar");
365              
366             On weighted graph, it's possible to pass the weight of the edge as a third
367             argument
368              
369             $g->add_edge("Foo", "Bar", 3);
370              
371             =head2 delete_edge
372              
373             $g->delete_edge(x, y)
374              
375             Removes the edge from x to y, if it is there.
376              
377             =head2 neighbors
378              
379             my @neighbors = $g->neighbors('u');
380              
381             Lists all vertices y such that there is an edge from x to y.
382              
383             =head2 weight
384              
385             Accessor to the weight of the edge. If called with two arguments, return the
386             value previously set, with three arguments, set the weight:
387              
388             # reader
389             my $w = $g->weight('u', 'v');
390              
391             # setter
392             $g->weight('u', 'v', 42);
393              
394             =head2 is_adjacent
395              
396             $g->is_adjacent(x, y)
397              
398             Tests whether there is an edge from node x to node y.
399              
400             =head2 breadth_first_search
401              
402             Performs a BFS traversal on the graph, returns the parents hash produced.
403              
404             Callbacks can be given to trigger code when edges or vertices are
405             discovered/processed.
406              
407             $g->breadth_first_search($vertex,
408             cb_vertex_discovered => sub { print "discovered vertex @_" },
409             cb_vertex_processed => sub { print "processed vertex @_" },
410             cb_edge_discovered => sub { print "new edge: @_" });
411              
412             =head2 depth_first_search
413              
414             Performs a DFS traversal on the graph, returns the parents hash produced.
415              
416             Callbacks can be given to trigger code when edges or vertices are
417             discovered/processed.
418              
419             $g->breadth_first_search('Foo',
420             cb_vertex_discovered => sub { print "discovered vertex @_" },
421             cb_vertex_processed => sub { print "processed vertex @_" },
422             cb_edge_discovered => sub { print "new edge: @_" },
423             );
424              
425             =head2 prim
426              
427             Implementation of the Prim algorithm to grow a Minimum Spanning Tree of the
428             graph.
429              
430             Return the tree produced (as a C object).
431              
432             my $mst = $g->prim('Foo');
433              
434             =head2 dijkstra
435              
436             Implementation of the Dijkstra algorithm to find all possible shortest paths from
437             a vertex C<$s> to all other vertices of the graph.
438              
439             The return value of this method is a hashref containing the following entries:
440              
441             =over 4
442              
443             =item spanning_tree
444              
445             A L object representing the minimum spanning tree produced by the
446             Dijkstra traversal.
447              
448             =item distances
449              
450             An hashref containing for each vertices the distance between that vertex and the
451             source vertex.
452              
453             =back
454              
455             my $dijkstra = $g->dijkstra('E');
456              
457             =head2 shortest_path
458              
459             Return the shortest path between two vertices as a list of vertices.
460              
461             my @path = $g->shortest_path('A', 'E');
462             # eg: ( 'A', 'D', 'F', 'E' )
463              
464             Note that internally, this method uses the spanning tree produced by the
465             Dijkstra algorithm to compute the shortest path.
466              
467             =head1 SYNOPSYS
468              
469             my $g = Graph::Simple->new ( is_directed => 1, is_weighted => 1);
470              
471             $g->add_edge( 'Al', 'Bob', 2 );
472             $g->add_edge( 'Al', 'Jim', 3 );
473             $g->add_edge( 'Joe', 'Jim', 3 );
474            
475             $g->neighbors('Al');
476              
477             =head1 SEE ALSO
478              
479             This distribution has been written because when I looked on CPAN for an easy to
480             use and lightweight interface for manipulating graphs in Perl, I dind't find
481             something that fitted my expectations.
482              
483             Other distributions exist though:
484              
485             =over 4
486              
487             =item L
488              
489             A rather feature-rich implementation but with a complex API.
490              
491             =item L
492              
493             Less features than Graph but presumably faster. Appears to
494             be unmaintained since 2010 though.
495              
496             =item L
497              
498             Perl bindings to the C++ graph library Boost. Certainly the fastest
499             implementation but depends on C++, obviously.
500              
501             =back
502              
503             =head1 AUTHOR
504              
505             Alexis Sukrieh
506              
507             =head1 COPYRIGHT AND LICENSE
508              
509             This software is copyright (c) 2013 by Alexis Sukrieh.
510              
511             This is free software; you can redistribute it and/or modify it under
512             the same terms as the Perl 5 programming language system itself.
513              
514             =cut
515              
516              
517             __END__