在 Perl 中封装递归函数

发布于 2024-09-29 21:24:11 字数 1693 浏览 7 评论 0原文

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

#!/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;
}

正确的做法是什么?

I have a code that works well here:

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

强者自强 2024-10-06 21:24:11

我无法弄清楚您想要 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 捕获了对其自身的引用,因此它永远不会被清除(直到程序退出)。我现在使用 Scalar::Util 中的 weaken 函数< /a> (如 singfish 在 中提到的他的博客条目)以避免这种情况。 $strongRef 仅用于防止 coderef 在我们使用完毕之前被垃圾回收。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文