如何使用已定义的生成器构建惰性列表,是否有“takeWhile”替代方法?

时间:2016-12-17 02:00:54

标签: perl6

我正在阅读perl6intro on lazy lists,这让我对某些事情感到困惑。

举个例子:

sub foo($x) {
  $x**2
}

my $alist = (1,2, &foo ... ^ * > 100);

会给我(1 2 4 16 256),它将相同的数字直到它超过100.我希望这给我(1 4 9 16 25 .. ),所以不要使用相同的数字来推进数字{{1通过1(或另一个给定的“步骤”),x等等。

在这种特定情况下是否可以实现这一目标?

我对懒惰列表的另一个问题如下: 在Haskell中,有一个takeWhile函数,Perl6中是否存在类似的东西?

3 个答案:

答案 0 :(得分:5)

以下是编写Perl 6等效Haskell takewhile的方法。

sub take-while ( &condition, Iterable \sequence ){
  my \iterator = sequence.iterator;

  my \generator = gather loop {
    my \value = iterator.pull-one;
    last if value =:= IterationEnd or !condition(value);
    take value;
  }

  # should propagate the laziness of the sequence
  sequence.is-lazy
  ?? generator.lazy
  !! generator
}

我可能还应该展示dropwhile的实现。

sub drop-while ( &condition, Iterable \sequence ){
  my \iterator = sequence.iterator;

  GATHER: my \generator = gather {

    # drop initial values
    loop {
      my \value = iterator.pull-one;

      # if the iterator is out of values, stop everything
      last GATHER if value =:= IterationEnd;

      unless condition(value) {
        # need to take this so it doesn't get lost
        take value;

        # continue onto next loop
        last;
      }
    }

    # take everything else
    loop {
      my \value = iterator.pull-one;
      last if value =:= IterationEnd;
      take value
    }
  }

  sequence.is-lazy
  ?? generator.lazy
  !! generator
}

这些只是刚开始运作的例子。

可以说这些值得作为列表/迭代的方法添加。

您可以(但可能不应该)使用序列生成器语法实现这些。

sub take-while ( &condition, Iterable \sequence ){
  my \iterator = sequence.iterator;
  my \generator = { iterator.pull-one } …^ { !condition $_ }
  sequence.is-lazy ?? generator.lazy !! generator
}
sub drop-while ( &condition, Iterable \sequence ){
  my \end-condition = sequence.is-lazy ?? * !! { False };
  my \iterator = sequence.iterator;

  my $first;
  loop {
    $first := iterator.pull-one;
    last if $first =:= IterationEnd;
    last unless condition($first);
  }

  # I could have shoved the loop above into a do block
  # and placed it where 「$first」 is below

  $first, { iterator.pull-one } … end-condition
}

如果它们被添加到Perl 6 / Rakudo中,它们很可能会用Iterator类实现 (我可能只是去添加它们。)

直接实现您所要求的是:

do {
  my $x = 0;
  { (++$x)² } …^ * > 100
}

可以使用状态变量来完成:

{ ( ++(state $x = 0) )² } …^ * > 100

在声明它之外不使用的状态变量不需要名称 (标量变量以未定义的Any开头,在数字上下文中变为0)

{ (++( $ ))² } …^ * > 100
{ (++$)² } …^ * > 100

如果需要初始化匿名状态变量,可以使用与等号元运算符//结合的已定义或运算符=

{ (++( $ //= 5))² } …^ * > 100

在一些简单的情况下,您不必告诉序列生成器如何计算下一个值 在这种情况下,结束条件也可以简化。

say 1,2,4 ...^ 100
# (1 2 4 8 16 32 64)

唯一可以安全地简化结束条件的时间是知道它会在值上停止。

say 1, { $_ * 2 } ... 64;
# (1 2 4 8 16 32 64)

say 1, { $_ * 2 } ... 3;
# (1 2 4 8 16 32 64 128 256 512 ...)

答案 1 :(得分:4)

  

我希望这能给我(1 4 9 16 25 ..)

获得该序列的最简单方法是:

my @a = (1..*).map(* ** 2);  # using a Whatever-expression
my @a = (1..*).map(&foo);    # using your `foo` function

...或者如果您更喜欢以类似于Haskell / Python列表理解的方式编写它:

my @a = ($_ ** 2 for 1..*);  # using an in-line expression
my @a = (foo $_ for 1..*);   # using your `foo` function

虽然可以通过...运算符(如Brad Gilbert's answerraiph's answer演示)来表达此序列,但它并不真正有意义,因为该运算符的目的是生成序列,其中每个元素都使用一致的规则从前一个元素派生。

为每项工作使用最佳工具:

  • 如果序列最容易迭代表达 (例如Fibonacci序列)
    使用...运算符。

  • 如果序列最容易表达为闭合公式 (例如正方形序列)
    使用mapfor

答案 2 :(得分:3)

  

我希望这能给我(1 4 9 16 25 ..)

my @alist = {(++$)²} ... Inf;
say @alist[^10]; # (1 4 9 16 25 36 49 64 81 100)

{...}是一个任意的代码块。当用作the ... sequence operator的LHS时,会为序列的每个值调用它。

大括号内的(...)²求值为parens内表达式的平方。 (我本可以写(...) ** 2来表示同样的事情。)

++$通过将预增量1, 2, 3, 4, 5, 6 ...(添加一项)与a $ variable相结合来返回++

  

在Haskell中,有一个takeWhile函数,在Perl6中是否存在类似的东西?

将上述序列中的Inf替换为所需的结束条件:

my @alist = {(++$)²} ... * > 70; # stop immediately after past 70
say @alist; # [1 4 9 16 25 36 49 64 81]

my @alist = {(++$)²} ...^ * > 70; # stop immediately before past 70
say @alist; # [1 4 9 16 25 36 49 64]

(注意序列运算符的......^变体如何提供停止条件的两个含义。我在原始问题中注意到... ^ * > 70。{{1这个与^分离,所以它有不同的含义。现在我不知道那是什么意思,但我需要去睡觉。:))