File Coverage

blib/lib/Graph/Implicit.pm
Criterion Covered Total %
statement 103 118 87.2
branch 10 18 55.5
condition 3 3 100.0
subroutine 25 27 92.5
pod 10 10 100.0
total 151 176 85.8


line stmt bran cond sub pod time code
1 6     6   199180 use strict;
  6         14  
  6         258  
2 6     6   34 use warnings;
  6         16  
  6         619  
3             package Graph::Implicit;
4             our $VERSION = '0.03';
5              
6 6     6   7013 use Heap::Fibonacci::Fast;
  6         14171  
  6         279  
7 6     6   37266 use List::MoreUtils qw/apply/;
  6         27312  
  6         7518  
8              
9             =head1 NAME
10              
11             Graph::Implicit - graph algorithms for implicitly specified graphs
12              
13             =head1 VERSION
14              
15             version 0.03
16              
17             =head1 SYNOPSIS
18              
19             my $graph = Graph::Implicit->new(sub {
20             my $tile = shift;
21             map { [$_, $_->intrinsic_cost] }
22             $tile->grep_adjacent(sub { $_[0]->is_walkable })
23             });
24             my @reachable_vertices = $graph->vertices(current_tile());
25             my @reachable_edges = $graph->edges(current_tile());
26             my ($sssp_predecessors, $dest_vertex) = $graph->dijkstra(
27             current_tile(),
28             sub { is_target($_[0]) ? 'q' : 0 },
29             );
30             my @sssp_path = $graph->make_path($sssp_predecessors, $dest_vertex);
31              
32             =head1 DESCRIPTION
33              
34             This module implements several graph algorithms (for directed, weighted graphs)
35             that can run without having to specify the actual graph. This module models a
36             graph as an arbitrary coderef that maps vertices onto a list of adjacent
37             vertices and weights for the edges to those vertices. Vertices can be
38             represented by any string (or stringifyable piece of data), and don't need to
39             be specified ahead of time; the algorithms will just figure it out. This allows
40             objects to generally just work (they get stringified to
41             C<"Class=HASH(0xdeadbeef)">).
42              
43             Some caveats: working with complicated data structures which need deep
44             comparisons generally need additional help: C<[0, 1, 2] ne [0, 1, 2]>, since
45             those become different references. Also, since the graph isn't specified at
46             all, each method that is called on one needs a vertex to start traversing the
47             graph from, and any vertices not reachable from that vertex won't be found. A
48             few algorithms are also not able to be implemented as efficiently as possible,
49             since the entire graph isn't known ahead of time; for instance, finding all the
50             edges of the graph requires actually doing a graph traversal, rather than just
51             reading them out of the data structure, like you would do in an explicit graph
52             representation.
53              
54             =cut
55              
56             =head1 CONSTRUCTOR
57              
58             =head2 new(CODEREF)
59              
60             The constructor takes a single argument, a coderef. This coderef should take
61             something representing a vertex, and return a list of arrayrefs, one for each
62             adjacent vertex, which have the vertex as the first element and the weight of
63             the edge to that vertex as the second element. For example, if the graph has
64             three elements A, B, and C, and there is an edge of weight 1 from B to A and an
65             edge of weight 2 from B to C, then the coderef should return C<["A", 1], ["C",
66             2]> when called with C<"B"> as its argument.
67              
68             =cut
69              
70             sub new {
71 6     6 1 3841 my $class = shift;
72 6         13 my $edge_calculator = shift;
73 6         25 return bless $edge_calculator, $class;
74             }
75              
76             =head1 METHODS
77              
78             =cut
79              
80             # generic information
81              
82             =head2 vertices(VERTEX)
83              
84             Returns a list of all vertices reachable from the given vertex.
85              
86             =cut
87              
88             sub vertices {
89 16     16 1 38034 my $self = shift;
90 16         31 my ($start) = @_;
91 16         26 my @vertices;
92 16     90   115 $self->dfs($start, sub { push @vertices, $_[1] });
  90         152  
93 16         190 return @vertices;
94             }
95              
96             =head2 edges(VERTEX)
97              
98             Returns a list of all edges reachable from the given vertex.
99              
100             =cut
101              
102             # XXX: probably pretty inefficient... can we do better?
103             sub edges {
104 8     8 1 1607144 my $self = shift;
105 8         18 my ($start) = @_;
106 8         32 map { my $v = $_; map { [$v, $_] } $self->neighbors($v) }
  45         95  
  45         82  
  109         694  
107             $self->vertices($start);
108             }
109              
110             =head2 neighbors(VERTEX)
111              
112             Returns a list of neighbors (without weights) of the given vertex.
113              
114             =cut
115              
116             sub neighbors {
117 53     53 1 63653 my $self = shift;
118 53         66 my ($from) = @_;
119 53         148 return map { $$_[0] } $self->($from);
  130         593  
120             }
121              
122             # traversal
123              
124             sub _traversal {
125 32     32   56 my $self = shift;
126 32         503 my ($start, $code, $create, $notempty, $insert, $remove) = @_;
127 32         81 my $bag = $create->();
128 32         52 my %marked;
129             my %pred;
130 32         73 $pred{$start} = undef;
131 32         108 $insert->($bag, [undef, $start], 0);
132 32         81 while ($notempty->($bag)) {
133 468         1643 my ($pred, $vertex) = @{ $remove->($bag) };
  468         678  
134 468 100       1837 if (not exists $marked{$vertex}) {
135 180 50       648 $code->($pred, $vertex) if $code;
136 180 100       603 $pred{$vertex} = $pred if defined wantarray;
137 180         282 $marked{$vertex} = 1;
138 180         404 $insert->($bag, [$vertex, $$_[0]], $$_[1]) for $self->($vertex);
139             }
140             }
141 32         149 return \%pred;
142             }
143              
144             =head2 bfs(VERTEX[, CODEREF])
145              
146             Does a breadth-first search of the graph, starting at the given vertex. It runs
147             the given coderef (if it exists) on each vertex, as they are encountered.
148             Returns a hashref, whose keys are the encountered vertices, and whose values
149             are the predecessor in the breadth-first search tree.
150              
151             =cut
152              
153             sub bfs {
154 8     8 1 74285 my $self = shift;
155 8         20 my ($start, $code) = @_;
156             return $self->_traversal($start, $code,
157 8     8   19 sub { [] },
158 125     125   219 sub { @{ $_[0] } },
  125         321  
159 117     117   1030 sub { push @{ $_[0] }, $_[1] },
  117         645  
160 8     117   110 sub { shift @{ $_[0] } });
  117         106  
  117         252  
161             }
162              
163             =head2 dfs(VERTEX[, CODEREF])
164              
165             Does a depth-first search of the graph, starting at the given vertex. It runs
166             the given coderef (if it exists) on each vertex, as they are encountered.
167             Returns a hashref, whose keys are the encountered vertices, and whose values
168             are the predecessor in the depth-first search tree.
169              
170             =cut
171              
172             sub dfs {
173 24     24 1 48008 my $self = shift;
174 24         48 my ($start, $code) = @_;
175             return $self->_traversal($start, $code,
176 24     24   54 sub { [] },
177 375     375   488 sub { @{ $_[0] } },
  375         974  
178 351     351   1481 sub { push @{ $_[0] }, $_[1] },
  351         2155  
179 24     351   235 sub { pop @{ $_[0] } });
  351         300  
  351         678  
180             }
181              
182             #sub iddfs {
183             #}
184              
185             # minimum spanning tree
186              
187             #sub boruvka {
188             #}
189              
190             # XXX: this algo only works in its current form for undirected graphs with
191             # unique edge weights
192             #sub prim {
193             #my $self = shift;
194             #my ($start, $code) = @_;
195             #return $self->_traversal($start, $code,
196             #sub { Heap::Fibonacci::Fast->new },
197             #sub { $_[0]->count },
198             #sub { $_[0]->key_insert($_[2], $_[1]) },
199             #sub { $_[0]->extract_top });
200             #}
201              
202             #sub kruskal {
203             #}
204              
205             # single source shortest path
206              
207             =head2 dijkstra(VERTEX[, CODEREF])
208              
209             Runs the Dijkstra single source shortest path algorithm, starting from the
210             given vertex. It also takes a single coderef as an argument, which is called on
211             each vertex as it is encountered - this coderef is expected to return a score
212             for the vertex. If the returned score is C<'q'>, then the search terminates
213             immediately, otherwise it keeps track of the vertex with the highest score.
214             This returns two items: a predicate hashref like the return value of L
215             and L, and the vertex which was scored highest by the scorer coderef
216             (or the vertex that returned C<'q'>).
217              
218             =cut
219              
220             sub dijkstra {
221 3     3 1 33424 my $self = shift;
222 3         8 my ($from, $scorer) = @_;
223 3     9944   22 return $self->astar($from, sub { 0 }, $scorer);
  9944         13659  
224             }
225              
226             =head2 astar(VERTEX, CODEREF[, CODEREF])
227              
228             Runs the A* single source shortest path algorithm. Similar to L, but
229             takes an additional coderef parameter (before the scorer coderef), for the
230             heuristic function that the A* algorithm requires.
231              
232             =cut
233              
234             sub astar {
235 3     3 1 9 my $self = shift;
236 3         6 my ($from, $heuristic, $scorer) = @_;
237              
238 3         41 my $pq = Heap::Fibonacci::Fast->new;
239 3         50 my %neighbors;
240 3         9 my ($max_vert, $max_score) = (undef, 0);
241 3         10 my %dist = ($from => 0);
242 3         7 my %pred = ($from => undef);
243 3         45 $pq->key_insert(0, $from);
244 3         20 while ($pq->count) {
245 5069         9938 my $cost = $pq->top_key;
246 5069         15673 my $vertex = $pq->extract_top;
247 5069 50       9896 if ($scorer) {
248 0         0 my $score = $scorer->($vertex);
249 0 0       0 return (\%pred, $vertex) if $score eq 'q';
250 0 0       0 ($max_vert, $max_score) = ($vertex, $score)
251             if ($score > $max_score);
252             }
253 5069 100       26716 $neighbors{$vertex} = [$self->($vertex)]
254             unless exists $neighbors{$vertex};
255 5069         60361 for my $neighbor (@{ $neighbors{$vertex} }) {
  5069         10227  
256 9944         10230 my ($vert_n, $weight_n) = @{ $neighbor };
  9944         16582  
257 9944         28169 my $dist = $cost + $weight_n + $heuristic->($vertex, $vert_n);
258 9944 100 100     45640 if (!defined $dist{$vert_n} || $dist < $dist{$vert_n}) {
259 5066         12167 $dist{$vert_n} = $dist;
260 5066         9353 $pred{$vert_n} = $vertex;
261 5066         22528 $pq->key_insert($dist, $vert_n);
262             }
263             }
264             }
265 3         7587 return \%pred, $max_vert;
266             }
267              
268             #sub bellman_ford {
269             #}
270              
271             # all pairs shortest path
272              
273             #sub johnson {
274             #}
275              
276             #sub floyd_warshall {
277             #}
278              
279             # non-trivial graph properties
280              
281             =head2 is_bipartite(VERTEX)
282              
283             Returns whether or not the reachable part of the graph from the given vertex is
284             bipartite.
285              
286             =cut
287              
288             sub is_bipartite {
289 0     0 1 0 my $self = shift;
290 0         0 my ($from) = @_;
291 0         0 my $ret = 1;
292 0         0 BIPARTITE: {
293 0         0 my %colors = ($from => 0);
294 6     6   70 no warnings 'exiting';
  6         12  
  6         1604  
295             $self->bfs($from, sub {
296 0     0   0 my $vertex = $_[1];
297             apply {
298 0 0       0 last BIPARTITE if $colors{$vertex} == $colors{$_};
299 0         0 $colors{$_} = not $colors{$vertex};
300 0         0 } $self->neighbors($vertex)
301 0         0 });
302 0         0 return 1;
303             }
304 0         0 return 0;
305             }
306              
307             # sorting
308              
309             #sub topological_sort {
310             #}
311              
312             # misc utility functions
313              
314             =head2 make_path(HASHREF, VERTEX)
315              
316             Takes a predecessor hashref and an ending vertex, and returns the list of
317             vertices traversed to get from the start vertex to the given ending vertex.
318              
319             =cut
320              
321             sub make_path {
322 100     100 1 458 my $self = shift;
323 100         134 my ($pred, $end) = @_;
324 100         93 my @path;
325 100         181 while (defined $end) {
326 10000         9974 push @path, $end;
327 10000         18021 $end = $pred->{$end};
328             }
329 100         7374 return reverse @path;
330             }
331              
332             =head1 BUGS
333              
334             No known bugs.
335              
336             Please report any bugs through RT: email
337             C, or browse to
338             L.
339              
340             =head1 TODO
341              
342             =over
343              
344             =item dijkstra/astar and bfs/dfs should have more similar interfaces - right now bfs/dfs just call a coderef and do nothing with it, while dijkstra/astar use the coderef to search for a vertex
345              
346             =item Several more graph algorithms need implementations
347              
348             =item Returning two values from dijkstra and astar is kind of ugly, need to make this better
349              
350             =back
351              
352             =head1 SEE ALSO
353              
354             L
355              
356             =head1 SUPPORT
357              
358             You can find this documentation for this module with the perldoc command.
359              
360             perldoc Graph::Implicit
361              
362             You can also look for information at:
363              
364             =over 4
365              
366             =item * AnnoCPAN: Annotated CPAN documentation
367              
368             L
369              
370             =item * CPAN Ratings
371              
372             L
373              
374             =item * RT: CPAN's request tracker
375              
376             L
377              
378             =item * Search CPAN
379              
380             L
381              
382             =back
383              
384             =head1 AUTHOR
385              
386             Jesse Luehrs
387              
388             =head1 COPYRIGHT AND LICENSE
389              
390             This software is copyright (c) 2009 by Jesse Luehrs.
391              
392             This is free software; you can redistribute it and/or modify it under
393             the same terms as perl itself.
394              
395             =cut
396              
397             1;