SICP练习2.33问题

时间:2014-07-06 22:47:42

标签: lambda scheme lisp sicp

这个练习有点问题。具体来说,'看'lambda表达式是如何工作的

练习本身就是这样说的..

(define (map p sequence)
(accumulate (lambda (x y) <??>) nil sequence))

必须变成这个......

(define (map p sequence)
   (accumulate (lambda (x y) (cons (p x) y)) null sequence))

但我不明白。我的意思是,我看到'accumulate'程序遵循(定义(累积op初始序列)... form

那么,这是否意味着(lambda(x y)(cons(p x)y))是'op'部分? &安培;若然,x&amp; y&amp;他们如何进入等式?

(体面的解释非常感谢)

3 个答案:

答案 0 :(得分:2)

首先,让我们看看书中定义的accumulate

(define (accumulate op initial sequence)
  (if (null? sequence)
      initial
      (op (car sequence)
          (accumulate op initial (cdr sequence)))))

基本上,它是foldr(又名fold-right)的临时实现,是处理输入列表的高阶过程,应用某个操作在每个元素上并累积结果。

请注意,op参数是一个如下所示的过程(让我们将xy重命名为更有意义的内容:

(lambda (element accumulator) <body>)

在上面,element表示输入列表中的当前元素,每个元素按从左到右的顺序依次处理,accumulator是累积值。输出,当我们遍历列表时递归构建。 <body>部分通过对当前元素和累积值执行某些操作来处理更新累积值。现在,让我们来看看我们通常如何写map

(define (map p sequence)
  (if (null? sequence)
      null
      (cons (p (car sequence))
            (map p (cdr sequence)))))

你注意到这种模式吗? mapaccumulate非常非常相似,我们只需要:

  1. 传递适当的initial值:null将是完美的
  2. 提供op程序,将p应用于列表中的当前元素,cons将其应用于递归调用的结果
  3. 当我们使用恰当的参数调用accumulate时,这正是我们所做的:

    (define (map p sequence)
      (accumulate (lambda (element accumulator)
                    (cons (p element) accumulator))
                  null
                  sequence))
    

    要注意的关键洞察力是lambda正文中的这一行:

    (cons (p element) accumulator)
    

    与原始map中的其他行完全相同

    (cons             (p (car sequence))            (map p (cdr sequence)))
     ^^^^             ^^^^^^^^^^^^^^^^^^            ^^^^^^^^^^^^^^^^^^^^^^
     cons both parts  apply `p` on current element  all this is the accumulated value
    

    要了解原因,请使用替换模型,并将opinitialsequenceaccumulate中的参数)替换为我们作为参数传递的实际值。

答案 1 :(得分:1)

  

所以,这是否意味着(lambda(x y)(cons(p x)y))是&#39; op&#39;部分?

  

&安培;若然,x&amp; y&amp;他们如何进入等式?

x&#34;当前元素&#34; y&#34;递归调用(accumulate ...)的结果在列表的其余部分&#34;

这就是这意味着什么。每个给定的列表都被视为一个缺点 - 一对car及其cdr。列表car的值进入xcdr - 进入递归调用,其结果进入y。或者是等式的,

accumulate( op, z, CONS(e,es) ) = op( e, accumulate(op, z, es) )

其中CONS(e,es)不是函数调用,而是数据表示(使用大写表示这一点) - {{1>} e中的{em> cons单元和car中的es(阅读: eez ,如 e,复数)。因此,当调用cdr时,会将op(x,y) = ...x = e传递给它。

上面的等式定义了函数y = accumulate(op, z, es)。对于accumulate case

,还需要一个等式
NIL

因此假设accumulate( op, z, NIL ) = z 是二进制操作(即接收两个参数),能够处理op的结果作为其第二个参数。这种列表处理模式称为"folding",或者#34; catamorphism&#34; - 即处理数据 down ,将数据分析成其组成部分,并以某种方式重新组合它们,安排某个&#34;呼叫协议&#34;用于二进制操作。

还有其他模式,例如一个所谓的&#34; paramorphism&#34;,

accumulate

这里假设操作是三元的(即接收三个参数),接收当前元素,输入列表的其余部分,以及输入列表的其余部分的递归处理的结果。它在实现需要访问输入和输出的数据处理模式时很有用(有时候会引用,隐藏的,&#34;有你的蛋糕和吃它的#34;)。 / p>

为了更有用,这些定义需要编码lazily,因此accumulate2( op, z, NIL ) = z accumulate2( op, z, CONS(e,es) ) = op( e, es, accumulate2(op, z, es) ) 可以选择是否强制递归结果,以便能够提前中止(参见例如{{3 }})。

答案 2 :(得分:0)

在SICP这一部分的一些练习中,对我有很多帮助的是引入了一些print语句来查看accumulate或其他函数调用中发生的事情,这些函数是递归调用的。

就像那样:(我正在使用Racket所以我不确定printf是否也在其他方案中定义了)

(define (accumulate op initial seq)
  (printf "op: ~a, initial: ~a, seq: ~a\n" op initial seq)
  (if (null? seq)
      initial
      (op (car seq)
          (accumulate op initial (cdr seq)))))