Encapsulating Recursive Function in Perl
I have a code that works well here:
#!/usr/bin/per开发者_如何学Gol
use strict;
use warnings;
use Data::Dumper;
my %graph =(
F => ['B','C','E'],
A => ['B','C'],
D => ['B'],
C => ['A','E','F'],
E => ['C','F'],
B => ['A','E','F']
);
sub findPaths {
my( $seen, $start, $end ) = @_;
return [[$end]] if $start eq $end;
$seen->{ $start } = 1;
my @paths;
for my $node ( @{ $graph{ $start } } ) {
my %seen = %{$seen};
next if exists $seen{ $node };
push @paths, [ $start, @$_ ] for @{ findPaths( \%seen, $node, $end ) };
}
return \@paths;
}
my $start = "B";
my $end = "E";
print "@$_\n" for @{ findPaths( {}, $start, $end ) };
What I want to do is to generate a more general subroutine
so that it just take \%graph, $start,$end
as input and return final array.
I tried to do it this way but it doesn't compile.
sub findPathsAll {
my ($graph,$start,$end) = @_;
my $findPaths_sub;
$findPaths_sub {
my( $seen) = @_;
return [[$end]] if $start eq $end;
$seen->{ $start } = 1;
my @paths;
for my $node ( @{ $graph{ $start } } ) {
my %seen = %{$seen};
next if exists $seen{ $node };
push @paths, [ $start, @$_ ] for @{ &$findPaths_sub( \%seen, $node, $end ) };
}
return \@paths;
}
my @all;
push @all,@$_ for @{ &$findPaths_sub( {}, $start, $end ) };
return @all;
}
What's the right way to do it?
I can't figure out what you want findPathsAll
to return, so this may not be quite what you want. Anyway, here's a translation of findPaths
into a lexical recursive coderef:
use Scalar::Util 'weaken';
sub findPathsAll {
my ($graph,$start,$end) = @_;
my $findPaths_sub;
my $strongRef = $findPaths_sub = sub {
my( $seen, $start, $end ) = @_;
return [[$end]] if $start eq $end;
$seen->{ $start } = 1;
my @paths;
for my $node ( @{ $graph->{ $start } } ) {
my %seen = %{$seen};
next if exists $seen{ $node };
push @paths, [ $start, @$_ ]
for @{ $findPaths_sub->( \%seen, $node, $end ) };
}
return \@paths;
};
weaken($findPaths_sub); # Prevent memory leak
my @all;
push @all,@$_ for @{ $findPaths_sub->( {}, $start, $end ) };
return @all;
## The above turns all the paths into one big list of nodes.
## I think maybe you meant this:
# return @{ $findPaths_sub->( {}, $start, $end ) };
## which would return a list of arrayrefs, one for each path.
}
Some notes:
You declare a coderef like this:
$var = sub { ... };
Note the assignment operator and the trailing semicolon. If you want the coderef to be recursive, you must have already declared $var
. (If you say my $var = sub { ... };
, the new variable doesn't exist until after the sub is created, so it can't refer to $var
.)
You call it like this:
$var->(arg1, arg2, ...);
(There are other syntaxes that work, but I think this is the preferred one.)
There was a subtle memory leak in the first version I posted. Perl uses a reference-count garbage collector, which means it can't delete self-referential data structures. Since the coderef in $findPaths_sub
captured a reference to itself, it would never be cleaned up (until program exit). I now use the weaken
function from Scalar::Util (as mentioned by singingfish in his blog entry) to avoid that. $strongRef
is used only to keep the coderef from being garbage collected before we're done with it.
I blogged about this very issue the other day.
精彩评论