Haskell 流式加工中的高效欧拉筛
euler的筛比Eratosthenes的筛具有更好的渐近复杂性,并且可以简单地用命令式语言实现 我想知道是否有任何方法可以用streams优雅地实现它。我已经检查了,但是这两种实现比wiki中的其他筛选(甚至是试用版!)慢数百倍 所以我试着自己写:Haskell 流式加工中的高效欧拉筛,haskell,primes,lazy-sequences,sieve,Haskell,Primes,Lazy Sequences,Sieve,euler的筛比Eratosthenes的筛具有更好的渐近复杂性,并且可以简单地用命令式语言实现 我想知道是否有任何方法可以用streams优雅地实现它。我已经检查了,但是这两种实现比wiki中的其他筛选(甚至是试用版!)慢数百倍 所以我试着自己写: {-sieve of Euler-} primesEU = 2:sieve [3,5 ..] where sieve (p:i:xt) = p:(sieve (i:xt) `minus` (lps i)) where lps
{-sieve of Euler-}
primesEU = 2:sieve [3,5 ..] where
sieve (p:i:xt) = p:(sieve (i:xt) `minus` (lps i)) where
lps i = map (i*) (takeWhile' (\p->(p<i)&&(mod i p /= 0)) primesEU)
takeWhile'::(t->Bool)->[t]->[t]
takeWhile' p [] = []
takeWhile' p (x:xs) = case (p x) of
True -> x:takeWhile' p xs
False -> [x]
minus::(Ord t) => [t]->[t]->[t]
minus [] _ = []
minus xs [] = xs
minus xs@(x:xt) ys@(y:yt)
|x<y = x:(minus xt ys)
|x==y = minus xt yt
|x>y = minus xs yt
{Euler的筛-}
primesEU=2:筛[3,5..]式中
筛子(p:i:xt)=p:(筛子(i:xt)`减去`(lps i)),其中
lps i=map(i*)(takeWhile'(\p->)(p[x]
减::(Ord t)=>[t]->[t]->[t]
减[].[]
减xs[]=xs
减xs@(x:xt)ys@(y:yt)
|xy=负xs yt
减号与Data.List.Ord中的减号类似
takeWhile'
类似于takeWhile
,但有一个细微的区别:takeWhile
删除不满足谓词的第一个元素;takeWhile'
将接受它
lsp i
返回i和素数的乘积的有限流,不超过i的最小素数因子
遗憾的是,我的实现运行速度非常慢,我找不到罪魁祸首
无论如何,是否有可能在流处理风格中实现有效的欧拉筛选?或者该算法具有与流的本质相反的内在特性
primesEU = 2:sieve [3,5 ..] where
sieve (p:i:xt) = p:(sieve (i:xt) `minus` (lps i)) where
lps i = map (i*) (takeWhile' (\p->(p<i)&&(mod i p /= 0)) primesEU)
不考虑9素数。我立即展开多个列表,这减少了工作量。我们得到
(:)
/ \
3 (\\)
/ \
s 5 [9]
然后,右子树顶部的减号要求对s5
进行足够的计算,以判断它是否为空
(\\)
/ \
(:) [9]
/ \
5 (\\)
/ \
s 7 [15,25]
这是在一个步骤中完成的。(然后需要确定lps 3
是否为空,稍后类似地,但对于lps k
的每个成员,这只是一个步骤,所以我们可以忽略它。)然后减去
需要一个比较,将5冒泡到顶部,留下
(:)
/ \
5 (\\)
/ \
(\\) [9]
/ \
s 7 [15,25]
现在,在消费了5之后,需要展开顶部(:)
的右子级
(\\)
/ \
(\\) [9]
/ \
(:) [15,25]
/ \
7 (\\)
/ \
s 9 [21,35,49]
一次比较,将7移过5的倍数
(\\)
/ \
(:) [9]
/ \
7 (\\)
/ \
(\\) [15,25]
/ \
s 9 [21,35,49]
再举一次,使其超过3的倍数
(:)
/ \
7 (\\)
/ \
(\\) [9]
/ \
(\\) [15,25]
/ \
s 9 [21,35,49]
在移动了前两个组合列表中的7个之后,它就可以使用了。在此之后,必须对顶级(:)
的右子树进行进一步评估,以确定下一个素数(如果有)。评估必须从(\\)的三个级别开始
在树中,到达提供下一个候选项的s9
(\\)
/ \
(\\) [9]
/ \
(\\) [15,25]
/ \
(:) [21,35,49]
/ \
9 (\\)
/ \
s 11 [27]
该候选人必须经过两次减号
es,才能最终达到消除该候选人的减号
(\\)
/ \
(\\) [9]
/ \
(:) [15,25]
/ \
9 (\\)
/ \
(\\) [21,35,49]
/ \
s 11 [27]
(\\)
/ \
(:) [9]
/ \
9 (\\)
/ \
(\\) [15,25]
/ \
(\\) [21,35,49]
/ \
s 11 [27]
现在减号可以第一次完成它的工作,产生
(\\)
/ \
(\\) []
/ \
(\\) [15,25]
/ \
(\\) [21,35,49]
/ \
s 11 [27]
(顶部的xs`minus`[]
只有在第一个参数被确定为非空后才消除空列表;交换minus
的前两个等式会改变这一点,但所需步骤的差异将很小)。然后,评估必须在树中向下四级以生成下一个候选值(11) 。该候选项必须经过四个减号
,直到到达顶部。在此过程中,一个减号
从树中删除,因此下一个候选项(13)只在树的四个层次上生成,而不是五个层次((13-3)/2
)因此,将p
带到顶部以使其被消耗的步骤数并不完全是(p-3)/2
,但也不是更少
lps k
中的最后一个元素总是可以被k
的最小素数因子的平方整除,它们都是奇数,因此最多有
1/2*(n/3² + n/5² + n/7² + n/11² + ...) ~ n/10
当到达n
时可以删除的列表(有一些更少,因为这会计算所有具有平方素数因子的数字,以及那些具有一个以上倍数的数字)
因此,问题在于,每个素数都是在树的深处产生的,至少从树的顶部开始产生p*0.4
级别。这意味着将p
提升到树的顶部使其可消费至少需要p*0.4
步骤,基本上是二次总体复杂度,不计算产生倍数列表所需的工作量
然而,实际行为一开始更糟糕,当计算100000到200000之间区域的素数时,它接近二次行为-应该成为极限的二次模对数因子,但我认为我没有耐心等待一百万或两百万
无论如何,是否有可能在流处理风格中实现有效的欧拉筛选?或者该算法具有与流的本质相反的内在特性
我不能证明这是不可能的,但我知道没有有效的方法来实现它。既不能产生一个素数流,也不能产生一个给定极限的素数列表
埃拉托什尼筛网之所以有效,是因为它很容易达到素数的下一个倍数(只需在索引中添加p
或2*p
左右即可)不考虑是否已将其作为较小素数的倍数删除。避免多次删除所需的工作远远超过多次删除所需的工作。“并且可以在命令式语言中简单高效地实现。”这对我来说是个新闻。你如何用命令式语言有效地实现它?@Danielfetcher我的错,我的英语不好检查一下实现的质量。@LouisWasserman中的实现速度大约是两倍,并且使用了大约一半的空间(伸缩性也更好)。我对相同算法的实现(因为NumberSieves
)大约三次(并且使用的内存比NumberSieves
少一点),更不用说arithmoi
中的Eratosthenes筛了<
1/2*(n/3² + n/5² + n/7² + n/11² + ...) ~ n/10