【问题标题】:How does reduce/scan work in APL with user defined functions?reduce/scan 如何在 APL 中使用用户定义的函数工作?
【发布时间】:2014-08-07 20:33:55
【问题描述】:

我试图在 APL 的布尔向量中找到最长的不间断 1 链的长度。在 Haskell 中,如果我有一个由 1 和 0 表示的布尔列表,我可以这样做:

Prelude> scanl (\acc x -> x*(acc+1)) 0 [0,0,1,1,0,1,1,1,1,0,1]
[0,0,0,1,2,0,1,2,3,4,0,1]

然后取最大值。

我尝试在 APL 中做类似的事情:

{⍵×⍺+1}\0 0 1 1 0 1 1 1 1 0 1
0 0 1 2 0 4 8 16 32 0 64

这根本不是我期望的向量。我曾假设 ⍺ 将在扫描/减少的上下文中引用累加器,而 ⍵ 将引用向量的下一个元素,但我的理解似乎有点偏离。这些简单的例子也让我感到困惑:

{⍵}\1 1 1 1 1
1 1 1 1 1
{⍵+⍵}\1 1 1 1 1
1 2 4 8 16
{⍵×⍵}\1 1 1 1 1
1 1 1 1 1

是否甚至可以(实际上)在 APL 中使用带有扫描/减少的用户定义函数?如果是这样,它是如何工作的,⍺和⍵指的是什么?

【问题讨论】:

    标签: haskell functional-programming apl


    【解决方案1】:

    首先,让我们看一下基本问题。 ⍺ 和 ⍵ 是匿名函数的左右参数:

          1 {⍺} 2
    1
          1 {⍵} 2
    2
          1 {⍺+⍵} 2
    3
    

    Reduce 和 scan 可以与用户定义的函数一起使用,而且它们经常如此。他们的工作方式是这样¹:

    f/1 2 3 4 … ←→ 1 f 2 f 3 f 4 …
    f\1 2 3 4 … ←→ (f/1) (f/1 2) (f/1 2 3) (f/1 2 3 4) …
    

    使用这些定义,让我们评估一下让您感到困惑的示例:

    {⍵}\1 1 1 1 1
    ({⍵}/1) ({⍵}/1 1) ({⍵}/1 1 1) … 
    1 (1 {⍵} 1) (1 {⍵} (1 {⍵} 1)) …
    1 1 (1 {⍵} 1) …
    1 1 1 …
    
    {⍵+⍵}\1 1 1 1 1
    ({⍵+⍵}/1) ({⍵+⍵}/1 1) ({⍵+⍵}/1 1 1) ({⍵+⍵}/1 1 1 1) …
    1 (1 {⍵+⍵} 1) (1 {⍵+⍵} (1 {⍵+⍵} 1)) (1 {⍵+⍵} (1 {⍵+⍵} (1 {⍵+⍵} 1))) …
    1 (1+1) (1 {⍵+⍵} (1+1)) (1 {⍵+⍵} (1 {⍵+⍵} (1+1))) …
    1 2 (1 {⍵+⍵} 2) (1 {⍵+⍵} (1 {⍵+⍵} 2)) …
    1 2 (2+2) (1 {⍵+⍵} 4) …
    1 2 4 (4+4) …
    1 2 4 8 …
    
    {⍵×⍵}\1 1 1 1 1
    …
    

    这里要注意的重要一点是,根据通常的 APL 评估规则,每个缩减都是从右到左完成的。将此与 Haskell 的 scanl 进行比较,后者返回一个连续的列表减少的值,其中每个减少都是从从左到右完成的:

    scanl f z [x1,x2,…] == [z,z `f` x1,(z `f` x1) `f` x2,…]
    

    因此,使用scanl 评估第二个示例,我们得到:

    scanl (\x y -> y+y) 1 [1,1,1,1,1]
    [1,(\x y -> y+y) 1 1,(\x y -> y+y) ((\x y -> y+y) 1 1) 1,…]
    [1,1+1,(\x y -> y+y) 1+1,…]
    [1,2,(\x y -> y+y) 2 1,…]
    [1,2,1+1,…]
    [1,2,2,…]
    

    Haskell 的 scanr1scanl1 的从右到左的对偶,与 APL 的扫描类似。 (以1 结尾的函数仅不同之处在于它们不需要起始值):

    scanr1 (\x y -> y+y) [1,1,1,1,1]
    […,(\x y -> y+y) 1 ((\x y -> y+y) 1 1),(\x y -> y+y) 1 1,1]
    […,(\x y -> y+y) 1 (1+1),1+1,1]
    […,(\x y -> y+y) 1 2,2,1]
    […,2+2,2,1]
    […,4,2,1]
    

    APL 的扫描实际上介于scanlscanr 的功能之间。虽然归约本身是从右到左完成的,如scanr,但它返回从left开始的中间归约,如scanl

    f\1 2 3 4 ←→ 1 (1 f 2) (1 f (2 f 3)) (1 f (2 f (3 f 4)))
    

    现在,当我们评估您尝试的解决方案时,应该清楚会发生什么:

    {⍵×⍺+1}\0 0 1 1 0 1 1 1 1 0 1
    ({⍵×⍺+1}/0) ({⍵×⍺+1}/0 0) ({⍵×⍺+1}/0 0 1) ({⍵×⍺+1}/0 0 1 1) …
    0 (0 {⍵×⍺+1} 0) (0 {⍵×⍺+1} (0 {⍵×⍺+1} 1)) (0 {⍵×⍺+1} (0 {⍵×⍺+1} (1 {⍵×⍺+1} 1))) …
    0 (0×1) (0 {⍵×⍺+1} (1×1)) (0 {⍵×⍺+1} (0 {⍵×⍺+1} (1×2))) …
    0 0 (0 {⍵×⍺+1} 1) (0 {⍵×⍺+1} (0 {⍵×⍺+1} 2)) …
    0 0 (1×1) (0 {⍵×⍺+1} (1×2)) …
    0 0 1 (0 {⍵×⍺+1} 2) …
    0 0 1 (1×2) …
    0 0 1 2 …
    

    关于您实际尝试做的事情,当然有很多解决方案。这是我想出的第一个(v 是零和一的向量):

    ⌈/¯1+2-⍨/(v=0)/⍳⍴v←0,v,0
    

    由于答案已经很长了,我将把它作为练习来分析。正如我所说,这只是我想到的第一件事,并使用了与您不同的方法。我相信一些更有经验的 APLer 会想出一个更好、更优雅的解决方案。

    编辑:这是一个尽可能接近您的原始方法的解决方案。由于某种原因,我无法让它与 GNU APL 一起工作,这是我通常使用的实现,所以我花了一点时间。这可能是一个错误,但我不知道。不幸的是,GNU APL 开发人员不太喜欢动态函数之类的语言扩展,因此可能是故意的。有时间我会调查的。它在 NGN 和 Dyalog 中对我有用。也许它可以进一步改进,但这几乎是你想做的:

          v ← 0 0 1 1 0 1 1 1 1 0 1
    0 0 1 1 0 1 1 1 1 0 1
          {⍺×⍵+1}/¨⌽¨,\v
    0 0 1 2 0 1 2 3 4 0 1
          ⌈/{⍺×⍵+1}/¨⌽¨,\v
    4
    

    (编辑:正如 Elias 在下面的评论中指出的那样,您可以通过将归约包装在匿名函数中来使其在 GNU APL 中工作:{{⍺×⍵+1}/⍵}¨⌽¨,\v。)

    第二次编辑:这可能是我能想到的最优雅的解决方案:

          v ← 0 0 1 1 0 1 1 1 1 0 1
          ⌈/∊⍴¨⊂⍨v
    4
    

    (另外,请注意,上述评估中可能存在一些错误。计算机在这种繁琐的工作上比人类好得多。无论如何,要点应该很清楚。)

    ¹ 实际上,这是过于简单化了。此解释使用所谓的 insert 缩减样式。 实现也可能使用 enclose 缩减样式, 这会导致细微的差异。此外,这是简单的 O(n²) 扫描定义。实现通常会为关联函数使用更高效的实现。

    【讨论】:

    • 您的问题是由 GNU APL 不支持无点样式引起的。您可以像这样解决它:{{⍺×⍵+1}/⍵}¨⌽¨,\v
    • 感谢您指出这一点,@Elias。我会把它添加到答案中。我认为这会是这样的事情,但我不太确定。不久前,我已经在邮件列表的几篇冗长的帖子中指出了我对适当的词法范围 lambdas 和默认编程的立场。虽然我很高兴和感激现在有一个免费的 APL2 实现,但我也有点失望,因为没有兴趣实现这些非常适合 APL 编程风格的现代扩展。
    • 我不太确定这永远不会发生。已经指出,一旦核心 APL 实现可以被认为是“完整的”,就会考虑扩展。
    • 好吧,虽然我确信会发生 一些 扩展,但我的印象是没有兴趣实现词法范围的 lambda(我认为这非常重要)或其他现代 FP 功能,如默认编程。然而,这不是详细说明这一点的正确地方,尤其是在 Jürgen 和其他 ML 成员缺席的情况下。我当然希望你是对的,但我不会为此屏住呼吸。
    【解决方案2】:

    对于一个老派的 APLer,这个问题看起来很像partitioned plus reduction。在嵌套数组出现和出现之前,分区技术被大量使用,直到大约 1980 年代后期。有关详细信息,请参阅

    www.sudleyplace.com/APL/boolean.pdf(pPLRED 第 10 页和其辅助函数 N-delta 第 15 页)

    http://www.chilton.com/~jimw/apl85.html(搜索 pPLUSRED)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-06
      • 1970-01-01
      • 2020-11-30
      • 1970-01-01
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多