我尝试在Scheme中使用fold实现map。 我引用了这个网站:[ref1] http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Towards_a_Standard_Library 我理解fold =>的方式 left-associate fold(foldl)从第一个元素折叠/迭代。 (向前) 右联合折叠(foldr)从最后一个元素折叠/迭代。 (向后) [Q1]我理解正确吗?
从ref1:foldl,foldr
的实现 `1) foldl (left-associate fold)`
(define (foldl func accum lst)
(if (null? lst)
accum
(foldl func (func accum (car lst)) (cdr lst))))
2) foldr (right-associate fold)
(define (foldr func end lst)
(if (null? lst)
end
(func (car lst) (foldr func end (cdr lst)))))
[=>]使用fold实现地图,ref1也有带折叠代码的地图,所以它就是..
1) map implementation using foldr from ref1.
(define (map func lst)
(foldr (lambda (x y) (cons (func x) y)) '() lst))
**[Q2] map impelementation using foldl from myself**
(define (fold-map-left pr list)
(foldl
(lambda (sublist element) (cons (pr element) sublist))
'()
list)
what I get as the output...
e.g. (fold-map-left (lambda(n) (expt n n)) '(1 2 3 4))
output => (256 27 4 1): backwards
答案 0 :(得分:1)
首先,我认为你的foldl
实施有点偏,在Scheme中实现它的通常方法是(但请务必阅读@JoshuaTaylor的评论,以了解后果):
(define (foldl func accum lst)
(if (null? lst)
accum
(foldl func
(func (car lst) accum) ; here's the change
(cdr lst))))
关于第一个问题:两个折叠从左到右使用输入列表,但它们在构建输出结果的方式上有所不同:foldl
在开头累积第一个输入元素,然后在结果的顶部累积第二个元素,依此类推;如果我们将列表构建为输出,那么第一个元素将是最后一个(并使用此示例测试您自己的foldl
实现,以了解为什么我说它有点偏离):
(foldl cons '() '(1 2 3 4 5))
=> '(5 4 3 2 1)
另一方面,foldr
递归地前进,直到它到达空列表然后,随着递归开始展开,最后一个元素首先被累积,然后在结果顶部倒数第二个,等等 - 从而保持输入的相同顺序:
(foldr cons '() '(1 2 3 4 5))
=> '(1 2 3 4 5)
关于第二个问题:在map
方面实施foldr
会更简单,因为正如我们之前看到的那样,它保留了输入顺序。这与您的相同,但具有更好的格式和变量名称:
(define (map func lst)
(foldr (lambda (ele acc)
(cons (func ele) acc))
'()
lst))
但是,当然,我们可以使用foldl
并在结尾处反转结果(或者:在将列表传递给foldl
之前反转列表):
(define (map func lst)
(reverse
(foldl (lambda (ele acc)
(cons (func ele) acc))
'()
lst)))
同时拥有foldl
和foldr
的想法是什么?这是一种权衡(并且从性能的角度来看,请务必阅读@ ChrisJester-Young对实施问题的讨论的评论)。在构建输出时,foldr
将保留与输入相同的顺序,但它将生成一个递归过程,该过程在与输入大小成比例的空间中运行:注意func
已执行在调用递归调用之后。
另一方面,foldl
将以相反的顺序构建输出,但它将生成一个在恒定空间中运行的迭代过程:注意在调用递归调用后没有什么可做的,所以它是tail-recursive和Scheme经过优化,可以有效地处理这种递归。