【问题标题】:Learning Scheme, trying to implement map学习方案,尝试实现map
【发布时间】:2014-06-28 07:17:55
【问题描述】:

我尝试在 Scheme 中使用 with fold 来实现 map。 我引用了这个网站:[ref1]http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Towards_a_Standard_Library 我对折叠的理解方式=> 左关联折叠 (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 实现 map,ref1 也有带有 foldr 代码的 map,就是这样..

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 
  • 我知道左关联折叠折叠/向后迭代,所以它会先将最后一个放入输出...,那么我该怎么做才能以正确的顺序输出?我的代码还有什么问题吗?

【问题讨论】:

  • 你期望得到的输出是什么,你实际得到的输出是什么?

标签: scheme fold


【解决方案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 递归地前进,直到它到达空列表,然后,随着递归开始展开,最后一个元素首先被累积,然后是结果顶部的倒数第二个,等等on - 从而保持输入的相同顺序:

(foldr cons '() '(1 2 3 4 5))
=> '(1 2 3 4 5)

关于第二个问题:用foldr 来实现map 会更简单,因为正如我们之前看到的那样,它保留了输入顺序。这和你的一样,但格式和变量名更好:

(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 的 cmets,以从性能角度讨论实现问题)。 foldr 将在构建输出时保留与输入相同的顺序,但它会生成一个递归过程,该过程在与输入大小成比例的空间中运行:注意 func 是在之后执行的> 调用递归调用。

另一方面,foldl 将以相反的顺序构建输出,但它会生成一个在恒定空间中运行的迭代过程:请注意,在调用递归调用后没有什么可做的,所以它是尾部- recursive 和 Scheme 针对有效处理这种递归进行了优化。

【讨论】:

  • 对于映射来说,左右折叠是一种堆堆栈权衡:对于左折叠,您在最后保留一个临时列表以反转,或者您正在生成一个临时反转要折叠的列表。 SRFI 1 的作者 Olin Shivers 认为,在这种情况下,更喜欢堆栈(即右折叠)是一个更好的折衷方案,因为数据局部性(堆栈帧通常保持在一起,而 cons 单元格可能遍布整个节目)并且易于内存回收。当然,你已经知道了所有这些,但 OP 可能会觉得这很有用。
  • 我对堆/栈的评论当然假设了传统的调用机制;对于所有使用 CPS 的实现,好吧,无论如何,一切都可能是基于堆的。 :-)
  • 请注意,某些语言的功能与 OP 发布的内容相同:(foldl func (func accum (car lst)) (cdr lst))。这具有很好的属性,即在左关联折叠中,初始值/累加器位于二元运算符func 的 left 侧。因此,例如 fold(+,w,[x,y,z]) 变为 (((w+x)+y)+z)。反过来说,你最终会得到 (z+(y+(x+w))),它似乎很少声称(在我看来)是列表上的左关联折叠。但是,实现方式有很多变化。例如,这就是 Common Lisp 的 reduce 所做的。
  • @JoshuaTaylor 有些语言是 Scheme。在R6RS report: (fold-left cons '(q) '(a b c)) ; ==> ((((q) . a) . b) . c) 但在SRFI-1 (fold cons '(q) '(a b c)) ; ==> '(c b a q)
  • @Sylwester 和 Racket (foldl cons '(q) '(a b c)) => '(c b a q)。所以,没有就此达成协议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-23
相关资源
最近更新 更多