File Coverage

blib/lib/Algorithm/Functional/BFS.pm
Criterion Covered Total %
statement 33 33 100.0
branch 14 14 100.0
condition 8 9 88.8
subroutine 4 4 100.0
pod 2 2 100.0
total 61 62 98.3


line stmt bran cond sub pod time code
1             package Algorithm::Functional::BFS;
2              
3 2     2   164212 use common::sense;
  2         17  
  2         14  
4              
5 2     2   150 use Carp;
  2         4  
  2         3337  
6              
7             =head1 NAME
8              
9             Algorithm::Functional::BFS - A functional approach to the breadth-first
10             search algorithm.
11              
12             This implementation supports both cyclic and acyclic graphs but does not
13             support edge or vertex weighting.
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.01';
22              
23             =head1 SYNOPSIS
24              
25             use Algorithm::Functional::BFS;
26              
27             # Create your object.
28             my $bfs = Algorithm::Functional::BFS->new
29             (
30             adjacent_nodes_func => $some_func,
31             victory_func => $some_other_func,
32             );
33             # Get a list (ref) of all the routes from your start node to the node(s)
34             # that satisfy the victory condition.
35             my $routes_ref = $bfs->search($start_node);
36              
37             =head1 METHODS
38              
39             =cut
40              
41             =head2 new(%params)
42              
43             Create a new Algorithm::Functional::BFS object with the specified parameters.
44              
45             Required parameters:
46              
47             adjacent_nodes_func:
48             A function (reference to a sub) that, given a node, returns an array ref
49             of adjacent nodes. If the node has no adjacent nodes, this function must
50             return an empty array ref.
51              
52             victory_func:
53             A function (referenec to a sub) that, given a node, returns a value that
54             evaluates to true if and only if the node satisfies the victory condition
55             of this search.
56              
57             Optional parameters:
58              
59             include_start_node:
60             If this is a true value, then the start node is a candidate for the
61             victory condition. That is, if the start node matches the victory
62             condition, then a single route will be returned by the search algorithm,
63             and that route will contain only the start node.
64              
65             one_result:
66             If this is a true value, then the search stops after a single route is
67             found, instead of searching for all the routes that satisfy the victory
68             condition at the depth of the first route.
69              
70             =cut
71             sub new
72             {
73 12     12 1 39638 my ($class, %opts) = @_;
74              
75 12 100       109 confess 'Missing "adjacent_nodes_func" parameter' unless
76             $opts{adjacent_nodes_func};
77 11 100       64 confess 'Missing "victory_func" parameter' unless $opts{victory_func};
78              
79 10         58 my %self =
80             (
81             adjacent_nodes_func => $opts{adjacent_nodes_func},
82             victory_func => $opts{victory_func},
83             include_start_node => $opts{include_start_node},
84             one_result => $opts{one_result},
85             );
86              
87 10         71 bless(\%self, $class);
88             }
89              
90             =head2 search($start_node)
91              
92             Perform a breadth-first-search from the specified node until the depth at
93             which at least one node satisfies the victory condition.
94              
95             Returns an array ref of routes. Each route is an array ref of the nodes
96             that are along the route from the start node to the node at which the
97             victory condition was satisfied. Because this implementation works on
98             cyclic graphs, multiple routes may be returned (and, indeed, multiple
99             nodes at the same depth level may satisfy the victory condition). If the
100             "one_result" option was passed to the constructor, then only one route
101             will be returned, but it will still be encapsulated in another array ref.
102              
103             =cut
104             sub search
105             {
106 10     10 1 64 my ($self, $start_node) = @_;
107              
108 10 100       63 confess 'Start node must be defined' unless $start_node;
109              
110             # Short circuit if the start node matches the victory condition.
111 9 100 66     47 return [ [ $start_node ] ] if
112             $self->{include_start_node} && $self->{victory_func}->($start_node);
113              
114             # Quick-to-read list of nodes we've already seen.
115 7         27 my %seen = ( $start_node => 1 );
116              
117             # All the routes we've taken so far that are still valid. This list
118             # is used more-or-less like a queue.
119 7         23 my @candidates = ( [ $start_node ] );
120              
121             # The final route list result.
122 7         12 my @results;
123              
124             # Iterate until we have results or no candidates are left.
125 7   100     52 until (@results > 0 || @candidates == 0)
126             {
127             # Keep new candidates separate from all candidates so that we can use
128             # pop() in the while loop below.
129 29         39 my @new_candidates;
130              
131             # Keep track of the nodes we've seen this loop that aren't already in
132             # %seen. By keeping these lists separate per iteration, we can find
133             # multiple routes to the same target node.
134             my %seen_this_loop;
135              
136             # Iterate over each of the candidate routes we have.
137 29         77 while (my $candidate_ref = pop @candidates)
138             {
139             # Extract the most recent node from the current candidate.
140 54         94 my $cur_node = $candidate_ref->[@$candidate_ref - 1];
141              
142 54 100 100     267 if (@$candidate_ref > 1 && $self->{victory_func}->($cur_node))
143             {
144 6         46 push(@results, $candidate_ref);
145 6 100       28 last if $self->{one_result};
146             }
147             else
148             {
149             # Get the list of nodes adjacent to the current node.
150 48         388 my $adj_ref = $self->{adjacent_nodes_func}->($cur_node);
151              
152             # For each node adjacent to the current node, if it hasn't
153             # been seen before, add a route to it to the list of
154             # candidates.
155 48         743 while (my $adj_node = pop @$adj_ref)
156             {
157 101 100       504 next if $seen{$adj_node};
158 52         120 $seen_this_loop{$adj_node} = 1;
159              
160 52         122 my @new_route = ( @$candidate_ref, $adj_node );
161 52         236 push(@new_candidates, \@new_route);
162             }
163             }
164             }
165              
166 29         57 @candidates = @new_candidates;
167 29         324 %seen = ( %seen, %seen_this_loop );
168             }
169              
170 7         38 return \@results;
171             }
172              
173             =head1 AUTHOR
174              
175             Colin Wetherbee, C<< >>
176              
177             =head1 BUGS
178              
179             Please file issues at this project's GitHub repository site.
180              
181             =head1 LICENSE AND COPYRIGHT
182              
183             Copyright 2012 Colin Wetherbee.
184              
185             This program is distributed under the MIT (X11) License:
186             L
187              
188             Permission is hereby granted, free of charge, to any person
189             obtaining a copy of this software and associated documentation
190             files (the "Software"), to deal in the Software without
191             restriction, including without limitation the rights to use,
192             copy, modify, merge, publish, distribute, sublicense, and/or sell
193             copies of the Software, and to permit persons to whom the
194             Software is furnished to do so, subject to the following
195             conditions:
196              
197             The above copyright notice and this permission notice shall be
198             included in all copies or substantial portions of the Software.
199              
200             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
201             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
202             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
203             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
204             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
205             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
206             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
207             OTHER DEALINGS IN THE SOFTWARE.
208              
209              
210             =cut
211              
212             1;