【问题标题】:SICP Chapter 3.5.2 infinite streams integers definitionSICP第3.5.2章无限流整数定义
【发布时间】:2020-06-09 09:38:41
【问题描述】:

我正在阅读 SICP,但难以理解为无限流提供的一个示例:

https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5.2

我们可以通过使用诸如 add-streams 之类的操作来操作流来做更多有趣的事情,这会产生两个给定流的元素总和:62

(define (add-streams s1 s2)
  (stream-map + s1 s2))

现在我们可以如下定义整数:

(define integers (cons-stream 1 (add-streams ones integers)))

我显然可以理解 integers 定义背后的意图,但我正在努力在脑海中“模拟”这个流。先前的示例不是问题,因为状态的维护是明确的。比如这个例子:

(define (integers-starting-from n)
  (cons-stream n (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

我对整数的这个定义没有问题。

书中描述了ones的定义:

(define ones (cons-stream 1 ones))

这很像递归过程的定义:ones 是一对 car 为 1 且其 cdr 是评估 one 的承诺。评估 cdr 再次给我们一个 1 和一个评估的承诺,等等。

也许这条线让我失望了。 Ones 很简单,因为在每个stream-cdr 上都会对过程进行评估,并提供一个新的“1”和下一个承诺。

当我尝试将此推理应用于integers 时,我很难理解为什么结果流不是“1 2 2 2 2 2 ...”,因为整数会不断重新评估并基本上重新开始1.

编辑 我没有详细说明我的问题中是否要假设记忆是疏忽的。 SICP确实提到了答案中提出的二次行为问题,并以记忆delay函数的形式提供了解决方案:

(define (memo-proc proc)
  (let ((already-run? false) (result false))
    (lambda ()
      (if (not already-run?)
          (begin (set! result (proc))
                 (set! already-run? true)
                 result)
          result))))

然后定义延迟,以便 (delay) 等价于

(memo-proc (lambda () <exp>))

【问题讨论】:

  • 如果您难以在脑海中模拟它,为什么不使用笔和纸(或其他电子设备)来评估它? SICP教你Scheme使用的评估模型,所以这应该可以机械地做。如果你做了这件事还是不明白,你可以指出你没有得到哪些步骤。
  • 查看这个带有明确状态的替代streams implementation,看看它是否对你来说更清楚。有时(经常?)同时从两个不同的角度看待问题会有所帮助。

标签: stream scheme infinite sicp lazy-sequences


【解决方案1】:

如果我们的流被记忆,那么作为参数传递给add-streamsintegers 总是“落后”我们正在枚举的integers,因此它总是可以访问记忆的值。 (括号)中的数字显示了记忆值的使用:

整数:1,添加流/个数:1, 1, 1, 1, 1, 1, ... \整数:1, (2), (3), (4), (5), (6), ... === === === === === === 结果:1 2, 3, 4, 5, 6, 7, ... 记忆: 2, 3, 4, 5, 6,

如果我们的流没有被记忆,那么每次在integers 上调用stream-cdr 时,都会创建一个新系列的ones,并将其添加到所有先前的ones

integers                          1
    ones                              1,  1,  1,  1,  1,  1,  ...
    integers                          1
        ones                              1,  1,  1,  1,  1,  ...
        integers                          1
            ones                              1,  1,  1,  1,  ...
            integers                          1
                ones                              1,  1,  1,  ...
                integers                          1
                    ones                              1,  1,  ...
                    integers                          1
                        ones                              1,  ...
                        integers                          1
                                 ==  ==  ==  ==  ==  ==  ==  
                                  1,  2,  3,  4,  5,  6,  7,  ...

所以100 是通过将ones 元素相加99 次和integersstream-car 生成的,这是前99 次调用integers 的结果。

虽然第一个add-streams 只组合了两个流,但第二个流将(在返回1 之后)从新的add-streams 返回结果,第二个流将是另一个@ 的结果987654337@:

1, add-streams / 1,               1,               1, ...
               \ 1, add-streams / 1,               1, ...
                                \ 1, add-streams / 1, ...
                                                 \ 1, add-streams ...

所以add-streams,有点像使用cons 创建一个列表,正在创建一对流,其中第一个是一对流,第二个是另一对流。

没有记忆,这不是integers 的实际实现,因为它的性能是 O(n^2):

访问元素的时间 CPU 时间的要素 整数(毫秒) ========== ======== 第一个 0 第 2 次 0 第 4 次 0 8 日 0 16 日 0 32 日 47 64 日 78 128 313 第 256 名 1,171 第 512 名 4,500 1,024 17,688 2,048 日 66,609 4,096 272,531

【讨论】:

  • 我根本不相信这是正确的。我相信(take n integers) 是一个 O(n) 操作;根据这个答案,它必须是 O(n^2)。这可以被测试。在任何情况下,必须检查使用中的cons-stream(或等效地,force)的特定实现,并根据Scheme的评估遵循特定测试代码的评估步骤规则,一定要看。
  • 我不太确定了。我已经添加了一个答案,但它需要完成......
  • 我已经测试了性能并且没有记忆它是二次的。我还验证了在访问第 n 个元素时,add-streams 被调用了 n 次。 (正如预期的那样,因为integers 调用add-streamsintegers 也是add-streams 的参数之一)
  • 正确。实际上,非记忆流看起来像onesintegers 重新输入。手工做这件事很累(如我的回答所示),而且我没有工具可以帮我做这件事。这些细微的差别很重要。
  • 所以回顾一下,对于记忆流的实现,性能是线性的意味着onesintegers中的每一个只有一个一个流,而不是它们的级联如这个答案所示 - 级联确实发生在非记忆流中。
【解决方案2】:

通过最简单的非记忆流实现,我们得到:

(define (stream-map2 f s1 s2)
  (cons (f (car s1) (car s2)) 
    (lambda ()
      (stream-map2 f ((cdr s1)) ((cdr s2))))))

(define ones (cons 1 (lambda () ones)))

(define integers
    (cons 1 
      (lambda ()
        (stream-map2 + ones integers)))       ;; 1
  = 
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (stream-map2 + ones 
              (stream-map2 + ones integers))))))      ;; 2
  =
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (let ((i2 (stream-map2 + ones integers)))
              (stream-map2 + ones i2))))))

  = 
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (let ((i2 (cons (+ (car ones) (car integers))   ;; <---- 1
                        (lambda () 
                          (stream-map2 + ones 
                            (stream-map2 + ones integers))))))
              (cons (+ (car ones) (car i2))
                (lambda ()
                  (stream-map2 + ones ((cdr i2))))))))))
  = 
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (cons (+ (car ones) 
                     (+ (car ones) (car integers)))
              (lambda ()
                (stream-map2 + ones 
                  (stream-map2 + ones 
                    (stream-map2 + ones integers)))))))))     ;; 3
  =
    ....

我们确实看到了一个三角形,这里展开了二次计算。

【讨论】:

  • Will Ness,您为回答我的问题付出了巨大的努力。以目前的形式,以及@codybartfast 的回答,它的工作方式现在非常清楚。非常感谢您的麻烦!很遗憾,我不能将两者都标记为已接受的答案。希望两者都能为其他人提供关于其工作原理的挥之不去的困惑。
【解决方案3】:

请参阅foot note 56

cons-stream 必须是特殊形式。如果cons-stream 是一个过程,那么根据我们的评估模型,评估(cons-stream &lt;a&gt; &lt;b&gt;) 将自动导致评估&lt;b&gt;,这正是我们不希望发生的事情。

【讨论】:

    【解决方案4】:

    我在这里遗漏的部分是 integers 根本没有被重新评估。 add-streams 返回的承诺是每个输入流的stream-cdr。 前面提到的“状态”在反馈循环中保持不变。
    它相当令人费解,老实说,它的力量似乎仍然几乎是神奇的。

    【讨论】:

    • 如果 integers 没有被重新评估,那么接受的答案肯定是不正确的(add-streams 的唯一提及是在 integers 内部,而不是在 add-streams 本身内部)。跨度>
    • '前面提到的“状态”是在反馈循环中维护的。'你能展示一段具体的代码吗? integers 本身什么都不做。
    • @WillNess 我不再支持这个了。我会删除这个答案。我将尝试在纸上进行更彻底的模拟,看看是否可以更好地处理它。我对 Lisp 没有任何权威!
    • 但您的回答确实有道理。我只是想看一些具体的代码sn-p,所以我们有一些具体的东西要谈,而不是猜测。
    • 你们这些 cmets 肯定有助于推动我进一步理解这一点。当我写这句话时,我的理解充其量是微不足道的。我同意我自己的答案与接受的答案不一致。当我今天晚些时候有空闲时,我会尝试在纸上再次运行。
    猜你喜欢
    • 1970-01-01
    • 2013-06-05
    • 1970-01-01
    • 1970-01-01
    • 2021-08-24
    • 2017-02-07
    • 1970-01-01
    • 2019-09-02
    • 1970-01-01
    相关资源
    最近更新 更多