如何在 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 2
和a +.* 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
在这种情况下,输出序列将包含中间值和结果。
考虑以下简单代码:
a ← ⍳5
el1 ← a+2
el2 ← +/el1
el1 el2
┌─────────┬──┐
│3 4 5 6 7│25│
└─────────┴──┘
我只是将 2 添加到 a
并对数组求和,然后返回包含两个操作结果的数组。据我了解,在这种情况下,APL 解释器必须 遍历数组 .
这是在 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 2
和a +.* 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
在这种情况下,输出序列将包含中间值和结果。