SICP 第 3.5.2 章无限流整数定义



我正在阅读 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))

这很像递归过程的定义:一对汽车是 1,其 cdr 是评估 1 的承诺。评估 cdr 再次给我们一个 1 和一个评估 cdr 的承诺,依此类推。

也许这句话让我失望了。 一很简单,因为在每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))))

然后定义延迟,使得(延迟(等价于

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

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

整数: 1, 添加流/一: 1, 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

,如果我们的流没有被记住,那么每次调用stream-cdrintegers都会创建一个新的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是通过添加ones99 次的元素和integersstream-car生成的,该元素是之前 99 次调用integers的结果。

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

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 时间元素  整数(毫秒( ==========    ======== 1 st 0 2 nd 0 4 日 0 8日 0  16日 0  32 第 47 届  64 th 78 128 th 313 256 th 1,171 512 th 4,500 1024日 17688 2048 th 66,609 4096 号 272531

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

(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
=
....

事实上,我们看到一个三角形的二次计算在这里展开。

见脚注56。

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

我在这里遗漏的是integers根本没有被重新评估。add-streams返回的承诺是每个输入流的stream-cdr。 前面提到的"状态"在反馈循环中得到维护。
它非常令人费解,老实说,它的力量似乎仍然几乎是神奇的。

最新更新