了解Scheme中的fold-left和fold-right

时间:2015-09-21 03:07:10

标签: functional-programming scheme fold

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

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

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

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

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

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

3 个答案:

答案 0 :(得分:7)

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

#!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原始元素到累加器的过程,如果它不是,则只计算累加器

由于我认为这是作业,我将让你做实际的实施。快乐的黑客攻击。

答案 1 :(得分:1)

考虑一个fold函数的含义:它得到一个两个参数函数,让它称之为“迭代器”和一个列表,并以这种方式重复地将迭代器应用于列表的元素:at通用步骤,使用列表的当前元素调用函数,以及迭代器的先前应用程序的结果(在第一步,前一个结果只是fold的第三个参数)。

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

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

将结果作为右参数生成到下一个应用程序的“feed”。类似于fold-left,区别在于函数从列表的头部开始应用,并使用前一个结果作为左参数。

因此,如果必须以这种方式定义map,请考虑必须构建一个必须以上述方式应用的函数“iterator”。也就是说,该函数必须将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作为两个函数。考虑到迭代器的不同应用顺序,这些函数会更加复杂。

答案 2 :(得分:1)

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

(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函数本身。但其中一个会产生结果颠倒(哪一个?)。您可以通过调用reverse的后处理步骤解决此问题。