考虑以下简单的代码:
a ← ⍳5
el1 ← a+2
el2 ← +/el1
el1 el2
┌─────────┬──┐
│3 4 5 6 7│25│
└─────────┴──┘
我只是将{2}添加到a
并将数组相加,然后返回包含两个操作结果的数组。据我了解,在这种情况下,APL解释器必须在阵列上进行2次。
这是在APL中执行此操作的正确方法,还是语言具有某种累加器,类似于函数式编程语言提供的(这样我只能在阵列上运行一次)?
答案 0 :(得分:1)
我会说这是做到这一点的方法。但是在考虑性能时,优化一下可能会很有趣:
{(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% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
答案 1 :(得分:1)
因此,APL中没有任何特定的累积功能。我想大多数APLers将+ / A和X + 2视为原始操作,其中解释器必须在数组上重复迭代一个事实。
但是,假设您的APL系统支持用户定义的运算符,您可以轻松编写一种累积运算符。也就是说,如果目标是对数组进行单次传递。像
这样的东西ejs
代码类似于
(⍳5) (+ accum +) 2
这是一个非常简单的解决方案,可以正确使用+和x以及 fn1的标识为1 的其他函数。性能不如示例那样好。另请注意,解决方案开始类似于还原。
回到普通APL,如果你不需要中间结果, ∇ r←a(fn1 accum fn2)b;x
[1] r←0
[2] :For x :In a
[3] r←r fn1 x fn2 b
[4] :End
∇
就可以了。实际上,+/ a + 2
或+/ a x 2
的代码片段非常常见。同样对于矢量a,内部产品也可以工作,+/ a * 2
和a +.x 2
也不常见。
最后也是最不重要的,未来的优化APL解释器或编译器可以使用 loop fusion 在内部执行此操作,只需一个循环而不是两个循环。
答案 2 :(得分:1)
如果我理解您的问题,那么您首先要将+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
在这种情况下,输出序列将包含中间值和结果。