Stream 无法理解/可视化SICP流汉明数字程序
我基本上被困在了SICP的练习3.56中。问题是这样的: 练习3.56。一个著名的问题,首先由R.Hamming提出,是按升序不重复地枚举除2、3或5以外没有素因子的所有正整数。一个显而易见的方法是依次测试每个整数,看看它是否有除2、3和5以外的其他因子。但这是非常低效的,因为随着整数变大,满足要求的整数越来越少。作为替代方案,让我们调用所需的数字流S,并注意以下事实Stream 无法理解/可视化SICP流汉明数字程序,stream,scheme,lazy-evaluation,sicp,hamming-numbers,Stream,Scheme,Lazy Evaluation,Sicp,Hamming Numbers,我基本上被困在了SICP的练习3.56中。问题是这样的: 练习3.56。一个著名的问题,首先由R.Hamming提出,是按升序不重复地枚举除2、3或5以外没有素因子的所有正整数。一个显而易见的方法是依次测试每个整数,看看它是否有除2、3和5以外的其他因子。但这是非常低效的,因为随着整数变大,满足要求的整数越来越少。作为替代方案,让我们调用所需的数字流S,并注意以下事实 S以1开头。 (比例流S2)的元素也是S的元素 (比例流S3)和(比例流5S)也是如此 这些都是S 现在我们所要做的就是
- S以1开头。
- (比例流S2)的元素也是S的元素
- (比例流S3)和(比例流5S)也是如此
- 这些都是S
(define (merge s1 s2)
(cond ((stream-null? s1) s2)
((stream-null? s2) s1)
(else
(let ((s1car (stream-car s1))
(s2car (stream-car s2)))
(cond ((< s1car s2car)
(cons-stream s1car (merge (stream-cdr s1) s2)))
((> s1car s2car)
(cons-stream s2car (merge s1 (stream-cdr s2))))
(else
(cons-stream s1car
(merge (stream-cdr s1)
(stream-cdr s2)))))))))
提前感谢。这是我将其可视化的最佳尝试。但我确实很挣扎,感觉就像一条三头蛇在吃自己的尾巴
If we say the values of the stream S are s0, s1, s2, ..., then
initially we only know the first value, s0.
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 ? ? ? ? ? ? ? ? ? ?
But we do know the three scale-streams will be producing multiples of
these values, on demand:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 ? ? ? ? ? ? ? ? ? ?
scale-2: 2*1 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 3*1 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5*1 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
Merge will initially select the lowest of the numbers at the heads of
these three streams, forcing their calculation in the process:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 ? ? ? ? ? ? ? ? ? ?
scale-2: [2] 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 3 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
So s1 will now have the value 2:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 [2] ? ? ? ? ? ? ? ? ?
scale-2: 2*2 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 3 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
Merge will now select 3 as the minimum of 4, 3, and 5:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 ? ? ? ? ? ? ? ? ?
scale-2: 4 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: [3] 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
and will put it into the next slot in the result stream S, s2:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 [3] ? ? ? ? ? ? ? ?
scale-2: 4 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 3*2 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
Scale-2's head is selected again:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 [4] ? ? ? ? ? ? ?
scale-2: 2*3 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 6 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
And then 5 is selected from scale-5 and placed in the result:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 [5] ? ? ? ? ? ?
scale-2: 6 2*? 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 6 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5*2 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
Two streams have 6 at their head, both are consumed but only one 6
is placed in the result:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 5 [6] ? ? ? ? ?
scale-2: 2*4 2*? 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 3*3 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 10 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
And a few more iterations:
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 5 6 [8] ? ? ? ?
scale-2: 2*5 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 9 3*? 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 10 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 5 6 8 [9] ? ? ?
scale-2: 10 2*? 2*? 2*? 2*? 2*? 2*?
scale-3: 3*4 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 10 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
_________________________________________________________________
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 5 6 8 9 [10] ? ?
scale-2: 2*6 2*? 2*? 2*? 2*? 2*?
scale-3: 12 3*? 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 5*3 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 5 6 8 9 10 [12] ?
scale-2: 2*8 2*? 2*? 2*? 2*?
scale-3: 3*5 3*? 3*? 3*? 3*? 3*? 3*?
scale-5: 15 5*? 5*? 5*? 5*? 5*? 5*? 5*? 5*?
_________________________________________________________________
s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
S = 1 2 3 4 5 6 8 9 10 12 [15]
scale-2: 16 2*? 2*? 2*? 2*?
scale-3: 3*6 3*? 3*? 3*? 3*? 3*?
scale-5: 5*4 5*? 5*? 5*? 5*? 5*? 5*? 5*?
________________________________________________________________
因此,也许它更像是一条一头从三条尾巴交替咬伤的蛇。作为一个恰当的命名,
merge
不应该删除重复项,因为它的名字表明它是mergesort
的一部分,应该保留它们Union
是此类操作的更好名称,它通过增加唯一数列表来表示集合(此处),它应该通过删除只能来自两个参数的重复项来保留该约束
回到问题本身,让我们把它象征性地写为
S235={1}∪ 2*S235∪ 3*S235∪ 5*S235
(等等,什么?)我们甚至不会试图确定这些∪代码>执行他们的工作,甚至不按哪个顺序。甚至有多少个术语:
S23={1}∪ 2*S23∪ 3*S23
甚至
S2={1}∪ 2*S2
现在这看起来很简单。我们甚至可以在这里简单地实现A
和B
的联合,首先取A
的所有元素,然后——取B
。在这里,它可以很好地工作,因为在这个∪代码>的左输入:
{1} ----∪-->--->--S₂--.--->S₂
/ \
\______*2_______/
---<----<---
两个S₂图中的代码>,是相同的,因为我们在拆分点从中抽取的任何东西都不会影响它
这不是很有趣吗
那么,我们如何将3的倍数添加到它?一种方法是
S23=S2∪ 3*S23
为什么要增加订单?怎么用?为什么,这是∪代码>!你好,另一个发现的需求。无论从哪一侧进入,它都必须先产生较小的元素,然后再产生较大的元素
如果两者相等,该怎么办?在这项计划中,我们是否需要关注这个问题?这会在这里发生吗
不可能。因此我们可以实现∪
在这里作为一个合并
,而不是作为一个联合
(但是记住第一个发现的需求!——它仍然有效吗?需要吗?添加新的案例)Merge
应该比union
更有效,因为它不涉及相等的情况
对于5的倍数呢?我们继续,作为
S235=S23∪ 5*S235
{1}----∪-->--->--s₂--.---s₂----∪-->--->--s₂₃--.---s₂₃----∪-->--->--s₂₃₅--.--->s₂₃₅
/ \ / \ / \
\______*2_______/ \______*3________/ \_______*5________/
---现在您已经看到了答案,您是否尝试过在计算这些表达式之前,Scheme如何展开这些表达式?我认为这可能有助于您理解这里发生了什么(使用书中给出的cons stream的等效定义,在手动扩展时使用delay)。我建议您至少完成流的扩展,直到达到6(流中的最低数字,是两个不同因素的倍数)。尝试使用显式对象对其进行编码,表示为具有可变状态的闭包,该闭包显式地从供应商处提取输入以生成输出(作为生成器的一个可能模型)。您将在这里发现许多隐藏的东西、可能性和选择(参见Python的tee
函数及其复杂性)。然后,切换回流,您将能够了解流是如何自动完成的(和/或更好),甚至可以看到可能的流实现中的不同选择。非常感谢。很高兴知道我不是唯一一个觉得这很难想象的人。很好的回答,谢谢你,阿加尼特不是一条蛇。有两个。每一个在这里被称为merge
merge
在小范围内工作:它不知道其输入来自之前输出的内容。它只需查看两个输入头,比较它们,然后抽取最小的一个。两个合并被安排在一个(最短的)树中,因此较低的合并的输出作为较高的合并的正确输入。你认为如果我在我的答案中添加了完全遵循本书代码的图表,值得吗?我们必须在这里努力“在小范围内工作”,像对待递归一样“放手并有信心”:递归的全部目的不是试图看到整个机制工作,而是专注于较小的部分,确保不变量被保留,这样就可以将较小的部分合成为一个较大的部分
{1} ----∪-->--->--S₂--.--->S₂
/ \
\______*2_______/
---<----<---
S2 = {1} ∪ 2*{1} ∪ 2*2*{1} ;; == {1, 2, 4, 8, 16, 32, ...}
∪ 2*2*2*{1}
∪ 2*2*2*2*{1}
∪ ..........
{1} ----∪-->--->--S₂--.---S₂----∪-->--->--S₂₃--.--->S₂₃
/ \ / \
\______*2_______/ \______*3________/
---<----<--- ---<----<---
S23 = S2 ∪ 3*S2 ∪ 3*3*S2 ;; = S2 ∪ 3*( S2 ∪ 3*S2
∪ 3*3*3*S2 ;; ∪ 3*3*S2
∪ 3*3*3*3*S2 ;; ∪ 3*3*3*S2
∪ .......... ;; ∪ ........ ) !!
{1} ----∪-->--->--S₂--.---S₂----∪-->--->--S₂₃--.---S₂₃----∪-->--->--S₂₃₅--.--->S₂₃₅
/ \ / \ / \
\______*2_______/ \______*3________/ \_______*5________/
---<----<--- ---<----<--- ---<----<---
1 --->---\
cons-stream ->-- S ---.---> S
/----->---->--- *2 --->---\ / |
/ union ->--/ /
.-->-- *3 -->--\ / /
| union ->--/ /
.-->-- *5 -->--/ /
\ /
\__________<__________<__________<_________<_______________/