考虑以下简单的代码:
a ← ⍳5
el1 ← a+2
el2 ← +/el1
el1 el2
┌─────────┬──┐
│3 4 5 6 7│25│
└─────────┴──┘
我只是将 2 添加到a数组并将其相加,然后返回包含两个操作结果的数组。据我了解,在这种情况下,APL 解释器必须遍历数组 2 次。
这是在 APL 中执行此操作的正确方法还是该语言是否具有某种累加器,类似于函数式编程语言所提供的(这只会让我在数组上运行 1 次)?
如果我理解您的问题,那么您首先映射+2到a,然后通过添加来减少它。例如在 haskell 中:(注意 reduce 是foldl)
foldl (+) 0 $ map (2+) [1..5]
显然,如果在没有优化(例如循环融合)的情况下完成,这需要两次通过。
我怀疑这是一个提出错误问题的案例,而您真正要寻找的是scan(在某些语言中是accumulate)。
哈斯克尔:
scanl (+) 0 [1..5]
-- [0,1,3,6,10,15]
APL:
+\⍳5
⍝ 1 3 6 10 15
在这种情况下,输出序列将包含中间值和结果。
我会说这是做到这一点的方法。但是在考虑性能时,稍微优化一下可能会很有趣:
{(2×⍴⍵)++/⍵}⍳5
25
因为在这种情况下,您只需要遍历一次数组,然后再添加两个标量。但“优势”取决于数组的长度:
]runtime {(2×⍴⍵)++/⍵}⍳5 {+/2+⍳⍵}5 -compare
{(2×⍴⍵)++/⍵}⍳5 → 2.0E¯6 | 0% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
* {+/2+⍳⍵}5 → 1.4E¯6 | -29% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
对于短数组,添加到每个元素会更快。但是元素越多,您可以获得的收益就越多:
]runtime {(2×⍴⍵)++/⍵}⍳5 {+/2+⍳⍵}500 -compare
{(2×⍴⍵)++/⍵}⍳5 → 1.5E¯6 | 0% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
* {+/2+⍳⍵}500 → 2.4E¯6 | +59% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
]runtime {(2×⍴⍵)++/⍵}⍳5 {+/2+⍳⍵}5000 -compare
{(2×⍴⍵)++/⍵}⍳5 → 1.6E¯6 | 0% ⎕⎕⎕⎕
* {+/2+⍳⍵}5000 → 1.5E¯5 | +822% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
因此,APL 中没有任何特定的累积功能。我想大多数 APLers 将 +/A 和 X+2 视为原始操作,其中解释器必须反复迭代数组,这是一个不争的事实。
但是,假设您的 APL 系统支持用户定义的运算符,您可以轻松编写一种累加运算符。也就是说,如果目标是对数组进行单次传递。就像是
(⍳5) (+ accum +) 2
代码类似于
∇ r←a(fn1 accum fn2)b;x
[1] r←0
[2] :For x :In a
[3] r←r fn1 x fn2 b
[4] :End
∇
这是一个非常简单的解决方案,可以与 + 和 x 以及fn1 的标识为 1 的其他函数一起正常工作。性能不会像示例那样好。另请注意,解决方案开始类似于减少。
回到正常的 APL,如果您不需要中间结果,+/ a + 2就可以了。实际上,+/ a x 2或+/ a * 2的代码片段非常常见。同样对于向量 a,内积也可以工作,a +.x 2并且a +.* 2也并不少见。
最后也是最不重要的一点,未来优化的 APL 解释器或编译器可以使用循环融合在内部仅使用一个循环而不是两个循环来执行此操作。