关于Raku中数组生成序列的问题

Question on the array generating sequence in Raku

我在 rosettacode

看到了这段代码
my @pascal = [1], { [0, |$_ Z+ |$_, 0] } ... Inf;
.say for @pascal[^4];
# ==>
# [1]
# [1 1]
# [1 2 1]
# [1 3 3 1]

在显式生成器块中,我知道像列表扁平化 | 和 zip 运算符 Z+ 这样的独立运算符是如何工作的,但我很难理解它们如何合作生成下一个数组。有人可以详细解释它是如何工作的吗?谢谢。

注意:为简洁起见,代码略有重新排列,即它与 Rosetta 中的代码在表面上有所不同。

这是序列运算符的一个有点有趣的应用,因为它每次产生的值都是数组。所以:

  • [1] 是序列开头产生的第一个数组
  • 该块指示如何生成序列中的下一个数组。它有一个参数,即 previous 序列值
  • Inf 永远不会匹配,所以序列将永远持续下去

一个更简单的示例可能会有所帮助:序列 [1], [1,1], [1,1,1], ...。也就是说,我们想要生成一个数组,它是序列中的前一项,末尾有一个额外的 1。我们可以这样做:

my @ones = [1], { [|$_, 1] } ... Inf;
.say for @ones[^4];

制作中:

[1]
[1 1]
[1 1 1]
[1 1 1 1]

第一次调用块时,它在 $_ 中得到 [1]。我们用 | 滑动它,从而产生一个数组 [1, 1]。第二次传递给块,依此类推。

所以,分解[0, |$_ Z+ |$_, 0]。外部方括号是一个数组组合器。其余部分可以理解为 0, |$_|$_, 0 压缩,使用 + 运算符压缩内容。

第一个块调用将通过 [1],因此我们将压缩 0, 11, 0。使用运算符压缩成对应用它,这意味着它将计算 0 + 1, 1 + 0。整体结果是数组 [1,1].

对块的第二次调用得到 [1,1]。它形成列表 0, 1, 11, 1, 0,然后 zip 运算符再次形成成对加法,即 0 + 1, 1 + 1, 1 + 0。整体结果是数组 [1,2,1].

实际上,结果每次都会增加一个元素,这是对前一个结果的两两相加的结果,两端用零填充。

所以让我们来看看发生了什么。

首先是序列生成器...,它需要一个起始值列表、一个代码块本身和结束点。

它使用起始值生成列表中的每个下一项,所以让我们从 @pascal[0] 开始,这很简单:[1]

对于 @pascal[1],我们用 @pascal[0] 中的值调用代码块,如下所示:

sub ( $array ) { 
  [0, |$array Z+ |$array, 0] 
}

你会注意到我已经把它变成了一个子,这只是为了让我能更容易地解释事情。在匿名代码块中,$_ 是传入数据,我将其命名为 $array。那么 $array == [1] 时我们 运行 的代码是什么?

[0, |[1] Z+ |[1], 0] => [1,1] 

所以这是一个问题,如果我们不使用 | 会发生什么?

[0, [1] Z+ [1], 0] => [1,1] 

一样的!那么为什么它在那里?那么如果我们设置 $array == [3] 会发生什么?

[0, [3] Z+ [3], 0] => [1,1] 

奇怪?不,因为 Z+ 像这样转换:

[0, [3] Z+ [3], 0] => [0 + [3], [3] + 0] => [1,1] 

Z 通过在元素之间使用给定的运算符 + 压缩元素来创建一个新列表。 + 正在进行数值计算,数组的数值表示是其中元素的数量,在本例中两次都是 1。

这就是 | 滑移运算符的用武之地,它将给定的数组滑移到它所在的列表上下文中。让我们回到 @pascal[1]

[0, |[1] Z+ |[1], 0] => [0, 1 Z+ 1, 0] => [0 + 1, 1 + 0] => [1,1] 

好的.. 所以 @pascal[2] 正在调用同一个块但现在传入 [1,1]

[0, |[1, 1] Z+ |[1, 1], 0] => [0, 1, 1 Z+ 1, 1, 0] => [0 + 1, 1 + 1, 1 + 0] => [1,2,1] 

以此类推进入无限!

我希望这有助于解释发生了什么?