【问题标题】:SICP Exercise 2.33 issueSICP 练习 2.33 问题
【发布时间】:2014-07-16 16:07:12
【问题描述】:

这个练习有点问题。具体来说,“了解” 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))

但我不明白。我的意思是,我看到“累积”过程遵循(定义(累积操作初始序列)...形式

那么,这是否意味着 (lambda (x y) (cons (p x) y)) 是“操作”部分? & 如果是,x 和 y 是什么?它们是如何传递到方程中的?

(非常感谢体面的解释)

【问题讨论】:

标签: lambda scheme lisp sicp


【解决方案1】:

首先我们看一下书中定义的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是输出的累加值,即在遍历列表时递归构建。 &lt;body&gt; 部分负责更新累积值,方法是对当前元素和累积值执行某些操作。现在,我们来看看我们平时是怎么写map的:

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

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

  1. 传递适当的initial 值:null 将是完美的
  2. 提供一个op 过程,将p 应用于列表中的当前元素,并将conses 应用于递归调用的结果

这正是我们所做的,当我们使用正确的参数调用 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 中的参数)替换为我们作为参数传递的实际值。

【讨论】:

  • 干杯老兄。还看不到,但感谢您的帮助。正在努力
【解决方案2】:

那么,这是否意味着 (lambda (x y) (cons (p x) y)) 是“操作”部分?

是的。

& 如果是,x 和 y 是什么以及它们是如何传递到方程中的?

x“当前元素”y“递归调用列表其余部分的(accumulate ...) 的结果”。 p>

这就是这个意思。每个给定的列表都被视为一对缺点 - 一对 carcdr。列表的car 的值进入xcdr 的值进入递归调用,其结果进入y。或以等式风格,

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

其中CONS(e,es) 不是函数调用,而是数据表示(使用大写来表示这一点)- cons 单元格 在其care 中带有cares (阅读:eez,如 e,复数)在其cdr 中。所以当op(x,y) = ...被调用时,x = ey = accumulate(op, z, es)被传入其中。

上面的等式定义了函数accumulate。对于NIL case,还需要一个等式:

accumulate( op, z, NIL ) = z

因此假设op是一个二元运算(即接收两个参数),能够处理accumulate的结​​果作为它的第二个参数。这种列表处理模式被称为"folding",或“catamorphism” - 即处理数据向下,将数据分析为其组成部分,并以某种方式重新组合它们,以某种方式安排二进制操作的“调用协议”。

还有其他模式,例如所谓的“变形”,

accumulate2( op, z, NIL ) = z
accumulate2( op, z, CONS(e,es) ) = op( e, es, accumulate2(op, z, es) )

这里假设操作是三元的(即接收三个参数),接收当前元素,输入列表的其余部分,以及输入列表其余部分的递归处理结果。它在实现需要访问输入和输出的数据处理模式时很有用(有时神秘地称为“吃蛋糕也吃蛋糕”)。

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

【讨论】:

  • 干杯。正如我对上面那个家伙说的那样,一分钱还没有适当下降(但我一直在坚持,所以希望它最终会下降)
  • @user3435279 我的回答有帮助吗?我认为等式定义应该足够清楚。如果 recursion 是您的难题,请尝试 stackoverflow.com/a/19951540/849891stackoverflow.com/a/11666815/849891。调用自身的能力一开始可能看起来很神秘,但关键是被调用的不是“自我”——而是自我的副本。跨度>
  • 不,老兄,v 有帮助。至少今天。昨天的大脑不太容易接受,但是这个递归的东西和 SICP 很难(可能是我研究过的最难的东西,我研究过很多东西)
  • @user3435279 你知道这个关于如何煮鸡蛋的笑话吗?你需要一个锅、一个炉子、一个水龙头和火柴,然后你有一个程序如何在一个装满水的锅里煮鸡蛋,方法是把鸡蛋放在炉子上,用火柴点燃,对吧?如果你已经得到了一锅装满鸡蛋的水,而炉子已经点燃了怎么办?那么,本着代码重用 / 结构递归的精神,你如何煮鸡蛋呢?
  • @user3435279 好的,就是这样:你把鸡蛋拿出来,倒掉水,关掉炉子,剩下的就是你已经知道如何处理的情况了! :) IOW,不要试图看到大局;做小事。这是结构递归的态度:(define (boil-some-eggs situation) (cond ((basic-situation? situation) (pour-water-in-put-eggs-in-turn-the-stove-on! situation)) (else (let ((basic-situation (analyze-into-constituent-parts situation))) (boil-some-eggs basic-situation))))).
【解决方案3】:

在 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)))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-23
    • 2012-07-05
    • 2011-11-05
    • 2017-02-03
    • 2010-12-26
    • 2012-12-15
    • 2021-05-23
    相关资源
    最近更新 更多