该代码更容易(至少对我来说)阅读一些变量,暗示性地重命名,如
lazy val ps: Stream[Int] = 2 #:: Stream.from(3).filter(i =>
ps.takeWhile{p => p * p <= i}.forall{ p => i % p > 0});
这很自然地从左到右读取,因为
素数是2i
,并且从3开始的那些数字,即平方不超过 的所有素数 ,不均分(即没有一些非零余数)。p
i
i
以真正的递归方式,为了将这个定义理解为定义不断增加的素数流,我们假设它是这样的,并且从这个假设中我们看到没有出现矛盾,即定义的真实性成立。
之后唯一的潜在问题ps
是在定义流时访问流的时间。作为第一步,想象我们只是从某个地方神奇地提供了另一个素数流。然后,在看到定义的真实性之后,检查访问的时机是否正确,即我们从不尝试访问ps
未定义的区域;这会使定义停滞不前,毫无成效。
我记得在某个地方(不记得在哪里)读过类似下面的东西——一个学生和一个巫师之间的对话,
- 学生:哪些数是质数?
- 巫师:嗯,你知道第一个质数是多少吗?
- s:是的,它是2。
- w:好的(很快在一张纸上写下2 )。那么下一个呢?
- s:嗯,下一个候选人是3。我们需要检查它是否被任何平方不超过它的素数除,但我还不知道素数是什么!
- w:别担心,我给你。这是我知道的魔法;毕竟我是个巫师。
- s:好的,那么第一个质数是多少?
- w:(瞥了一眼纸)2。
- s:太好了,所以它的平方已经大于3了……嘿,你作弊了!......
这是您的代码的伪代码1翻译,部分从右到左读取,为清楚起见再次重命名了一些变量(p
用于“prime”):
ps = 2 : filter (\i-> all (\p->rem i p > 0) (takeWhile (\p->p^2 <= i) ps)) [3..]
这也是
ps = 2 : [i | i <- [3..], and [rem i p > 0 | p <- takeWhile (\p->p^2 <= i) ps]]
使用list comprehensions在视觉上更明显。and
检查布尔值列表中的所有条目是否为True
(读|
作“for”、<-
“drawn from”、,
“such that”和(\p-> ...)
“ lambda of p
”)。
所以你看,ps
是一个 2 的惰性列表,然后是i
从一个流中抽取的数字[3,4,5,...]
,使得对于所有p
从ps
这样抽取的数字p^2 <= i
,确实i % p > 0
。这实际上是一个最优的 试划分算法。:)
当然,这里有一个微妙之处:列表ps
是开放式的。我们使用它是因为它正在“充实”(当然,因为它是懒惰的)。当p
s 取自 时ps
,可能会出现超出其末尾的情况,在这种情况下,我们将进行非终止计算(“黑洞”)。就这样发生了:)(并且需要 ⁄ 可以在数学上证明)上述定义是不可能的。所以 2 被无条件地放入ps
,所以它有一些东西开始。
但如果我们尝试“简化”,
bad = 2 : [i | i <- [3..], and [rem i p > 0 | p <- takeWhile (\p->p < i) bad]]
它在只产生一个数字 2 后停止工作:当考虑 3 作为候选时,takeWhile (\p->p < 3) bad
要求bad
2 之后的下一个数字,但那里还没有更多的数字。它“超越自己”。
这是“固定”的
bad = 2 : [i | i <- [3..], and [rem i p > 0 | p <- [2..(i-1)] ]]
但这是一种慢得多的试验划分算法,与最优算法相去甚远。
--
1(实际上是Haskell,那样对我来说更容易:))