至少你的代码应该是
primes = [2,3,5,7,11,13]
genPrimes primes max = go primes (length primes) 1 (last primes + 2)
where
go prs len d t
| len >= max = prs
| (prs !! d) > (floor . sqrt . fromIntegral) t
= go (prs++[t]) (len+1) 1 (t + 2)
| t `rem` (prs !! d) == 0 = go prs len 1 (t + 2)
| t `rem` (prs !! d) /= 0 = go prs len (d + 1) t
test n = print $ genPrimes primes n
main = test 20
然后你重新组织它(抽象出为每个候选编号执行的测试,作为 noDivs 函数):
genPrimes primes max = go primes (length primes) (last primes + 2)
where
go prs len t
| len >= max = prs
| noDivs (floor . sqrt . fromIntegral $ t) t prs
= go (prs++[t]) (len+1) (t + 2)
| otherwise = go prs len (t + 2)
noDivs lim t (p:rs)
| p > lim = True
| t `rem` p == 0 = False
| otherwise = noDivs lim t rs
然后你将noDivs重写为
noDivs lim t = foldr (\p r -> p > lim || rem t p /= 0 && r) False
然后您会注意到 go 只是过滤了通过 noDivs 测试的数字:
genPrimes primes max = take max (primes ++ filter theTest [t, t+2..])
where
t = last primes + 2
theTest t = noDivs (floor . sqrt . fromIntegral $ t) t
但这还行不通,因为theTest 需要将primes(发现的全新素数)传递给noDivs,但我们正在构建这个@987654334 @list(如take max (primes ++ ...)),这样有没有恶性循环?不,因为我们只测试一个数字的平方根:
genPrimes primes max = take max wholePrimes
where
wholePrimes = primes ++ filter theTest [t, t+2..]
t = last primes + 2
theTest t = noDivs (floor . sqrt . fromIntegral $ t) t wholePrimes
这正在工作。但最后,genPrimes 现在没有什么特别之处了,它只是对take 的美化调用,而最初的primes 列表实际上可以缩小,所以我们得到(稍微改变noDivs 的参数排列,使它的界面更通用):
primes = 2 : 3 : filter (noDivs $ tail primes) [5, 7..]
noDivs factors t = -- return True if the supplied list of factors is too short
let lim = (floor . sqrt . fromIntegral $ t)
in foldr (\p r-> p > lim || rem t p /= 0 && r) True factors
-- all ((/=0).rem t) $ takeWhile (<= lim) factors
-- all ((/=0).rem t) $ takeWhile ((<= t).(^2)) factors
-- and [rem t f /= 0 | f <- takeWhile ((<= t).(^2)) factors]
全局 primes 列表现在已无限期定义(即“无限”)。 Next step 是意识到在素数的连续平方之间,要测试的因素列表的长度将是相同的,每个新段增加 1。 Then,将所有因子作为全局 primes 列表的前缀(已知长度),我们可以直接生成它们的倍数(因此每个因子都是从其主要因子生成的) ,而不是按顺序测试每个数字是否是其平方根以下的任何一个质因数的倍数。