【问题标题】:Understanding fold-left and fold-right in Scheme理解 Scheme 中的 fold-left 和 fold-right
【发布时间】:2015-12-17 15:39:21
【问题描述】:

所以我的任务是在 Scheme 中使用 fold-left 或 fold-right 实现最基本版本的 'map' 函数和 'filter' 函数。我很难理解这些功能到底在做什么。这是我所拥有的:

(define (myMap f l)
    (fold-left (lambda (f a) (f a)) '() l))

(define (myFilter f l)
    (fold-left f '() l))

底部是我直觉认为应该的。应用过滤器(比如 number? 到 l 的每个元素并将结果放入空列表中)。顶部是完全错误的,但我觉得那更在正确的轨道上。使用某种 lambda 函数将函数应用于数字?

这是我正在寻找的输出示例:

(myMap sqrt '(4 9 16 25)) ; (2 3 4 5)
(myFilter odd? '(1 2 3 4 5)) ; (1 3 5)

【问题讨论】:

  • 这里的诀窍是,与mapfilter 不同,fold 不一定返回一个列表。 fold 操作比 map 和 filter 更通用(正如可以根据 fold 定义它们的事实所证明的那样)。例如,通过使用折叠将列表(“reduce”是折叠的常见替代名称)减少为总和,可能更容易理解折叠的作用。试试(fold-left + 0 '(1 2 3 4 5)) 看看我的意思。
  • @AlexisKing 这是我在很多文档中找到的示例。我知道它像(... (+ (+ (+ 0 1) 2) 3) ...) 一样应用它,但我如何扩展它以使用任意函数做某事?我猜像(lambda (f l n) (append (f n) l)) 这样我可以将我应用函数的元素添加回列表?或者这没有意义
  • 不,问题是提供给fold 的过程的元素不是列表。您不能在非列表中调用 append。为了让fold 返回一个列表,您的累加器函数必须明确执行在累加的每一步中建立一个新列表的职责。不过,这就是折叠:它是一种函数式操作,它封装了遍历列表并将每个值复合到“累加器”值的想法。累加函数接收当前累加器和新值,并产生一个新的累加器。
  • @amza 不,就像(... (+ 3 (+ 2 (+ 1 0))) ...),用于左侧折叠。不是每个+ 都是可交换的(a+b == b+a 但 a-b != b-a)。
  • 虽然和你的问题没有直接关系,但我前段时间深入写过左右折叠:codereview.stackexchange.com/a/87626/216。它甚至包括一个非常类似于mapfilter 的东西的实现。

标签: functional-programming scheme fold


【解决方案1】:

fold-leftfold-right 都使用从第一个元素到最后一个元素的缩减过程减少一个或多个列表,但它的应用顺序保留在 fold-right 中,而在 fold-left 中颠倒。如果您执行以下操作,它很容易显示:

#!r6rs
(import (rnrs))

;; helper for R6RS fold-left since argument order is swapped
(define (xcons d a) 
  (cons a d))

(fold-left xcons '() '(1 2 3 4)) ; ==> (4 3 2 1)
(fold-right cons '() '(1 2 3 4)) ; ==> (1 2 3 4)

我创建xcons 的原因是left-fold 的累加器是第一个参数。在SRFI-1 List library 中,fold-left 等价只是被称为fold 并且具有与fold-right 相同的参数顺序:

(import (rnrs base)
        (only (srfi :1) fold fold-left))

(fold cons '() '(1 2 3 4))       ; ==> (4 3 2 1)
(fold-right cons '() '(1 2 3 4)) ; ==> (1 2 3 4)

左折叠是尾递归的,因为它处理第一个元素并成为下一次迭代的累加器。正确折叠需要将最后一个元素添加到累加器,然后再将倒数第二个元素一直添加到第一个元素。这意味着应尽可能避免右折叠,并且在许多情况下,如果结果的顺序或您折叠到单个值(例如找到最大元素),则左折叠是可以的。

对于mapfilter,您希望结果的顺序相同,因此您需要始终使用fold-right

对于map,您需要创建一个过程,cons 将提供的过程应用于具有累加器的元素的结果。这就是 fold-right 最终获得列表所需要的。您当前的解决方案没有任何 cons,因此您不会获得列表。

对于filter,如果谓词的结果为真值,则需要创建一个过程,将cons 的原始元素添加到累加器,如果不是,则只计算累加器。

因为我认为这是家庭作业,所以我会让你去做实际的实施。愉快的黑客攻击。

【讨论】:

    【解决方案2】:

    考虑fold函数的含义:它有一个有两个参数的函数,我们称之为“迭代器”和一个列表,并将迭代器重复应用于列表的元素,这样:在泛型步骤,使用列表的当前元素调用函数,以及之前应用迭代器的结果(在第一步,之前的结果只是fold的第三个参数)。

    因此,例如,fold-right 从列表的末尾开始,并且在每个“步骤”迭代器都以这种方式应用:

    (iterator current-element previous-result) ;; produces: next result
    

    将结果作为正确的参数“馈送”到下一个应用程序。与fold-left 类似,不同之处在于该函数从列表的头部开始应用,并使用先前的结果作为左参数。

    因此,如果您必须以这种方式定义map,请考虑您必须构建一个必须以上述方式应用的函数“迭代器”。也就是说,该函数必须将f 应用于当前元素并产生将在下一次迭代中使用的结果。由于我们要构建此应用程序的结果列表,因此迭代器的主体可以简单地为:

    (cons (f current-element) previous-result)
    

    整个函数变成:

    (define (myMap f lst)
      (define (iterator current-element previous-result)
        (cons (f current-element) previous-result))
      (fold-rigth iterator '() lst))
    

    由于这是一个练习,我将把 myFilter 的定义留给你:这次你必须找到一个合适的迭代器主体,使得当前元素仅在函数中“插入”到结果中应用于它返回true,否则必须忽略它。

    另一个有趣的练习是将fold-left 用于这两个函数。考虑到迭代器的不同应用顺序,这些函数稍微复杂一些。

    【讨论】:

      【解决方案3】:

      折叠表示预设的递归模式。左折叠从左到右遍历列表的所有元素,使用组合当前元素和当前累加器的函数转换累加器,以生成将在下一次调用中使用的累加器的下一个值组合函数,如

      (fold-left <+> [a,b,c..,n] z) === (n <+> (... <+> (c <+> (b <+> (a <+> z)))...))
      

      因此,

      (define (myMap-lt f lst)
        (fold-left 
          (lambda (elem     ; list's element
                   accum    ; accumulator so far
                   )
               ( ... (f elem)    ; map applies f on each elem
                    ... accum    ; accum holds list-of-results-so-far
                         ... )   ; need to extend it with this elem's result
             ) 
          '()               ; initial value of the accumulator
          lst))
      

      右边折叠也是类似的,虽然它结合了当前元素和递归处理的结果从右边

      (fold-right <+> [a,b,c..,n] z) === (a <+> (b <+> (c <+> (... <+> (n <+> z)...))))
      

      在这两种情况下,这里的组合函数实际上是相同的,cons 操作和f 函数本身的功能组合。但是其中一个会产生结果reversed(哪一个?)。您可以通过调用reverse 的后处理步骤来解决此问题。

      【讨论】:

        猜你喜欢
        • 2010-11-29
        • 1970-01-01
        • 1970-01-01
        • 2010-11-20
        • 1970-01-01
        • 1970-01-01
        • 2016-07-03
        • 1970-01-01
        • 2015-03-30
        相关资源
        最近更新 更多