我试图了解APL中的经典快速排序:
Q←{1≥≢⍵:⍵ ⋄ S←{⍺⌿⍨⍺ ⍺⍺ ⍵} ⋄ ⍵((∇<S)⍪=S⍪(∇>S))⍵⌷⍨?≢⍵}
有些事情我不理解,有些样式选择困扰我,所以我将列出所有这些。我希望有人可以向我解释。
{ }
定义中,⍺
是左参数,而⍵
是右参数。 ⍺⍺
中的S←{⍺⌿⍨⍺ ⍺⍺ ⍵}
是什么?同样,是否有⍵⍵
? ⍺
内的S
是指S
的 left参数还是Q
的 left参数? 我的猜测是⍺
中的S
是指S
的左参数。 ⍺⍺
指的是封闭函数的⍺
(即Q的⍺
)。
⍨
)?代码是否更清楚地写为:Q←{1≥≢⍵:⍵ ⋄ S←{(⍺ ⍺⍺ ⍵)⌿⍺} ⋄ ⍵((∇<S)⍪=S⍪(∇>S))⍵[?≢⍵]}
我能想到的通勤唯一用途是减少括号()
和[]
的使用,但这似乎不值得丢掉易读性。我在这里缺少“ APL方式”吗?
这实际上不是在执行快速排序,对吗? Quicksort定义为就地 。但是,我对APL语义的理解是,实际上这段代码在递归子调用上构建了新数组,并使用⍪
将它们连接起来。确实,这是same criticism that is levelled at Haskell's quicksort。我在APL语义中缺少什么东西来告知此操作是“就地”完成的吗?请注意,我对“足够智能的编译器”参数不感兴趣,因为数组分析从根本上具有挑战性。如果APL编译器确实将其转化为就地算法,我将非常重视其如何执行此分析的详细信息---这是一个很大的成就!
为什么使用≢⍵
来查找尺寸大小?为什么不⍴⍵
?通常,我发现人们使用≢
而不是⍴
来查询沿最外层尺寸的尺寸,即使函数只能在1D中使用,也是如此。再次,我认为我缺少APL方式的东西。
非常感谢。
答案 0 :(得分:3)
- 我知道在
{ }
定义中,⍺
是左参数,而⍵
是右参数。⍺⍺
中的S←{⍺⌿⍨⍺ ⍺⍺ ⍵}
是什么?同样,是否有⍵⍵
?
S←{⍺⌿⍨⍺ ⍺⍺ ⍵}
称为 dop 。与 dfn 是用户定义的函数类似, dop 是用户定义的运算符,其行为类似于¨
,⍨
或{{ 1}}。
其语义概述:
∘
(而不是⍺⍺
),则它将成为一元运算符,您可以将其用作⍵⍵
。x (m S) y
,它将成为二元运算符,您可以将其用作⍵⍵
。x (m S n) y
(将具有⍺⍺
的值)和m
(将具有⍵⍵
的值)可以是数组或函数。n
,在这种情况下,您可以将其命名为⍺
或(m S) y
,而忽略左侧的参数。(m S n) y
被称为 left操作数,而m
被称为 right操作数。与左参数(n
)和右参数(x
)不同。在您的示例中,y
仅提及S
,因此被称为⍺⍺
。如果您像x (m S) y
那样呼叫S
,它将求和为1 2 3 >S 2
,它将是一个3。
1 2 3⌿⍨1 2 3 > 2
中的⍺
是指S
的左自变量还是S
的左自变量?
在Q
的正文中,由S
和⍺
字符组成的所有内容均指⍵
的自变量/操作数。 S
的原始参数是不可见的(除非首先将它们分配给变量,在这种情况下,它们作为变量名可见)。
- 为什么大量使用通勤(
Q
)?
我认为这主要是一种风格选择。我还更喜欢编写带括号的代码,而不是在生产代码中使用它,除非当使用很容易被识别为APL习惯用法时。我确实写例如用⍨
代替3÷⍨whatever
进行常数除。
- 这实际上不是执行快速排序,对吗?
您是正确的。正如您已经提到的,Quicksort旨在就地运行以真正成为 Quick (TM)。 APL可以进行内存预分配和数组共享以减少一些内存副本和分配,但是当创建三个子数组(元素小于/等于/大于数据透视表)并创建并删除时,至少有一些副本是不可避免的。稍后串联。
要注意的一件事是,与Haskell不同,APL确实具有就地分配,看起来像(whatever)÷3
。如果要在APL中正确实现Quicksort,则必须像在C中那样编码(将索引传递给递归调用等)。
- 为什么使用
x[i]←v
来查找尺寸尺寸?为什么不≢⍵
?
⍴⍵
被称为“ tally”,而≢
被称为“ shape”。 ⍴
总是返回标量值,而≢
返回一个向量(如果给定向量参数,则长度为一个向量)。虽然标量和长度为一的矢量在大多数情况下的行为相同,但它们 却是不同的东西,例如⍴
是错误的。
我相信这是区分两者的好习惯,只要有可能,就优先选择标量而不是长度一的向量。一个值得注意的例外是当您需要显式封闭的数组(标量不能被封闭)时。