如何在 APL 中实现累积?

How do I implement accumulation in APL?

考虑以下简单代码:

  a ← ⍳5
  el1 ← a+2
  el2 ← +/el1 
  el1 el2

  ┌─────────┬──┐
  │3 4 5 6 7│25│
  └─────────┴──┘

我只是将 2 添加到 a 并对数组求和,然后返回包含两个操作结果的数组。据我了解,在这种情况下,APL 解释器必须 遍历数组 .

2 次

这是在 APL 中执行此操作的正确方法吗?或者该语言是否具有某种累加器,类似于函数式编程语言提供的累加器(这只会让我遍历数组 1 次)?

我会说这是做到这一点的方法。但是在考虑性能时,优化一下可能会很有趣:

{(2×⍴⍵)++/⍵}⍳5
25

因为在那种情况下,您只会遍历数组一次,然后再添加两个标量。但是 'advantage' 取决于数组的长度:

]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 中没有任何特定的累积功能。我想大多数 APLer 都将 +/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 2a +.* 2也很常见。

最后也是最不重要的一点,未来的优化 APL 解释器或编译器可以使用 loop fusion 仅用一个循环而不是两个循环在内部执行此操作。

如果我理解你的问题,那么你首先将 +2 映射到 a,然后通过加法将其缩减。 例如在 haskell 中:(注意 reduce 是 foldl

foldl (+) 0 $ map (2+) [1..5] 

显然,如果没有优化,这需要两次通过,例如循环融合。

不过

我怀疑这是问错问题的情况,而您实际要查找的是 scan(在某些语言中是 accumulate)。

Haskell:

scanl (+) 0 [1..5]
-- [0,1,3,6,10,15]

APL:

+\⍳5
⍝  1 3 6 10 15

在这种情况下,输出序列将包含中间值和结果。