4

我正在尝试使用 NestedLoops 函数在 Perl 中构建一个排列程序。这是我的代码:

use strict;
use warnings;
use Algorithm::Loops qw(NestedLoops);

my @a = 'a'..'o';

my $length = 5;
my $start = 0;
my $depth = 2;

NestedLoops([
  [0..$length],
  ( sub {
    $start = 0 if $start == $depth;
    $start++;
    [$start * $length..$start * $length + $length - 1]
  }) x $depth,
], \&permute,);

sub permute {
  my @ind = @_;
  foreach my $i (@ind) {
    print $a[$i];
  }
  print "\n";
}

所以我有一个数组,其中包含字母“a”到“o”(大小为 15)。我将数组视为有 3 行,所以我对数组的想象是这样的:

abcde
fghij
klmno

然后每个循环对应于每一行......我想构建如下排列:

afk
afl
afm
afn
afo
agk  // fails here... I end up getting agg
...

它适用于前 5 个值(最低 for 循环的整个运行),但随后第二次运行失败,因为最后一行的值$start被重置为 0......这是一个问题,因为这会破坏一切。

所以我想知道的是,我怎样才能$start根据级别保持持久的价值......所以我所要求的本质上是有常数。我的循环真的应该是这样的:

for my $a (0..5) {        # 0 at this level and never change
  for my $b (5..10) {     # $start should be 5 at this level and never change
    for my $c (10..15) {  # $start should be 10 at this level and never change
      permute($a, $b, $c);
    }
  }
}

现在,因为我将有可变长度的 for 循环,所以我无法对每个起始值进行硬编码,所以我正在寻找一种方法来最初创建这些起始值,然后在循环重置时保留它们。

我意识到这是一个令人困惑的问题,所以请提出问题,我将帮助澄清。

4

2 回答 2

4

你让这变得比它必须的更难。
部分问题在于NestedLoops的文档没有详细说明如何使用第一个参数中的子例程引用。


对于以下示例,假设这写在它们上方的某处。

use strict;
use warnings;
use Algorithm::Loops qw'NestedLoops';

真正调用NestedLoops来获得你想要的最简单的方法是这样的:

NestedLoops(
  [
    ['a'..'e'],
    ['f'..'j'],
    ['k'..'o'],
  ],
  \&permute
);

sub permute {
  print @_, "\n";
}

如果您真的希望即时生成 NestedLoops 的参数我建议您使用List ::MoreUtils中的部分

use List::MoreUtils qw'part';

my @a = 'a'..'o';

my $length = 5;
my $index;

NestedLoops(
  [
    part {
      $index++ / $length
    } @a
  ],
  \&permute
);

sub permute {
  print @_, "\n";
}

如果由于某种原因您想调用带有数组索引的NestedLoops ,使用part仍然很容易。

use List::MoreUtils qw'part';

my @a = 'a'..'o';

my $length = 5;

NestedLoops(
  [
    part {
      $_ / $length
    } 0..@a-1
  ],
  \&permute
);

sub permute {
  print map { $a[$_] } @_;
  print "\n";
}

实际上,您遇到的主要问题是您提供给NestedLoops的两个子例程引用正在修改相同的变量,并且它们都被多次调用。解决此问题的最佳方法是依赖在调用子例程时为其提供的最后一个值。(从实现来看,这似乎更接近它的用途。)

my @a = 'a'..'o';

my $length = 5;
my $depth = 3;

NestedLoops(
  [
    [0..$length-1],
    (sub{
      return  unless @_;
      my $last = pop;
      my $part = int( $last / $length ) + 1; # current partition
      my $start = $part * $length; # start of this partition
      my $end = $start + $length;
      [$start..$end-1] # list of variables in this partition
    }) x ($depth-1)
  ],
  \&permute
);

sub permute {
  print map { $a[$_] } @_;
  print "\n";
}
于 2012-04-06T06:36:49.873 回答
2

当您使用子例程生成循环范围时,每次嵌套循环之一必须启动时都会调用它。这意味着包含循环的每次迭代一次。在每次调用之前都$_设置为包含循环变量的当前值,并且所有包含循环变量的值作为参数传递。

为了澄清这一点,NestedLoops您编码的语句等效于

sub loop_over {
  $start = 0 if $start == $depth;
  $start++;
  [$start * $length..$start * $length + $length - 1]
};

NestedLoops([
  [0..$length],
  (\&loop_over) x $depth,
], \&permute,);

在原始 Perl 中,它看起来像

for my $i (0 .. $length) {

  $_ = $i;
  my $list = loop_over($i);

  for my $j (@$list) {

    $_ = $j;
    my $list = loop_over($i, $j);

    for my $k (@$list) {
      permute($i, $j, $k);
    }
  }
}

所以也许现在你的计算$start是错误的更清楚了?在执行上升以重新启动包含循环之前,它会为最内层重新评估几次。

由于传递给子例程的参数由包含循环变量的所有值组成,@_因此可以检查 的大小以查看循环的哪个级别生成范围。例如,在上面的代码中, if@_包含两个值,它们是$i$j,所以$k必须返回 for 的值;或者,如果只有一个参数,则为 的值$i,返回的值必须为 的范围$j。因此,您的正确值$start只是元素的数量,@_并且可以使用my $start = @_;.

使用这种方法,子程序也可以返回最外层循环的范围。代码看起来像这样

use strict;
use warnings;

use Algorithm::Loops qw(NestedLoops);

my @a = 'a'..'o';

my $length = 5;
my $start = 0;
my $depth = 2;

NestedLoops([
  (sub {
    $start = @_;
    [$start * $length .. $start * $length + $length - 1];
  }) x ($depth + 1)
], \&permute,);

sub permute {
  print map { $a[$_] } @_;
  print "\n";
}
于 2012-04-06T11:10:52.987 回答