Haskell 流式加工中的高效欧拉筛

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

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 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