封装Perl中的递归函数

时间:2010-11-01 06:19:52

标签: perl function subroutine

我有一个在这里运作良好的代码:

#!/usr/bin/perl
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 ) };

我想要做的是生成更通用的子程序 所以它只需要\%graph, $start,$end作为输入并返回最终数组。

我尝试这样做,但它没有编译。

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;
}

这样做的正确方法是什么?

2 个答案:

答案 0 :(得分:4)

我无法弄清楚你想要什么findPathsAll返回,所以这可能不是你想要的。无论如何,这里是findPaths到词法递归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.
}

一些注意事项:

你声明一个这样的coderef:

$var = sub { ... };

请注意赋值运算符和尾随分号。如果您希望coderef是递归的,那么您必须已经声明了$var。 (如果你说my $var = sub { ... };,新的变量在创建之后之前就不存在,所以它不能引用$var。)

你这样称呼它:

$var->(arg1, arg2, ...);

(还有其他语法可行,但我认为这是首选语法。)

我发布的第一个版本中有一个微妙的内存泄漏。 Perl使用引用计数垃圾收集器,这意味着它无法删除自引用数据结构。由于$findPaths_sub中的coderef捕获了对自身的引用,因此它永远不会被清除(直到程序退出)。我现在使用the weaken function from Scalar::Util(正如his blog entry中的singfish所提到的)来避免这种情况。 $strongRef仅用于防止coderef在我们完成之前被垃圾收集。

答案 1 :(得分:1)