找到所有先前的下一个素数

时间:2013-12-18 11:51:51

标签: algorithm generator primes lazy-evaluation sieve-of-eratosthenes

我正在写一个递归无限素数生成器,我几乎可以肯定我可以更好地优化它。

现在,除了前十几个素数的查找表外,每次调用递归函数都会收到所有先前素数的列表。

因为它是一个懒惰的生成器,所以现在我只是过滤掉任何前一个素数的模0的数字,并取出第一个未过滤的结果。 (检查我正在使用短路,所以第一次上一个素数均匀地划分当前数字时它会中止该信息。)

现在,在搜索第400个素数(37,813)时,我的表现会下降。我正在寻找方法来使用我有一个所有先前质数列表的独特事实,并且我只搜索下一个,以改进我的过滤算法。 (我能找到的大多数信息都提供了非懒惰的筛子来查找极限下的质数,或者找到p n th prime的方法给定p n-1 ,而不是优化找到p n 给出2 ... p n-1 primes。)

例如,我知道p n th prime必须位于范围内(p n-1 + 1)...(p n -1 + p <子>的n-2 )。现在我开始在p n-1 + 2处过滤整数(因为p n-1 + 1只能是p n-1的素数< / sub> = 2,这是预先计算的)。但由于这是一个懒惰的生成器,知道范围的终端边界(p n-1 + p n-2 )并没有帮助我过滤任何东西。

对于所有以前的素数,我能做些什么来更有效地过滤?

代码示例

  @doc """
  Creates an infinite stream of prime numbers.

    iex> Enum.take(primes, 5)
    [2, 3, 5, 7, 11]

    iex> Enum.take_while(primes, fn(n) -> n < 25 end)
    [2, 3, 5, 7, 11, 13, 17, 19, 23]

  """
  @spec primes :: Stream.t
  def primes do
    Stream.unfold( [], fn primes ->
      next = next_prime(primes)
      { next, [next | primes] }
    end )
  end

  defp next_prime([]),      do: 2
  defp next_prime([2 | _]), do: 3
  defp next_prime([3 | _]), do: 5
  defp next_prime([5 | _]), do: 7
  # ... etc

  defp next_prime(primes) do
    start = Enum.first(primes) + 2
    Enum.first(
      Stream.drop_while(
        Integer.stream(from: start, step: 2),
        fn number ->
          Enum.any?(primes, fn prime ->
            rem(number, prime) == 0
          end )
        end
      )
    )
  end

primes函数以空数组开头,获取它的下一个素数(最初为2),然后1)从流中发出它并且2)将它添加到顶部的素数在下一个电话中使用的堆栈。 (我确定这个堆栈是一些减速的来源。)

next_primes函数接收该堆栈。从最后一个已知的prime + 2开始,它创建一个无限的整数流,并删除每个整数,该整数均由列表的任何已知素数均匀分割,然后返回第一个匹配项。

我想这是类似于懒惰增量的Eratosthenes的筛子。

你可以看到一些基本的优化尝试:我开始检查p n-1 +2,然后我跳过偶数。

我通过在每次计算中传递Integer.stream来尝试更加逐字的Eratosthenes的筛子,并在找到一个素数后,将Integer.stream包装在一个新的Stream.drop_while中,只过滤掉该素数的倍数。但是由于Streams是作为匿名函数实现的,因此会破坏调用堆栈。

值得注意的是,我并不认为您需要所有先前的素数来生成下一个素数。由于我的实施,我碰巧碰到了它们。

3 个答案:

答案 0 :(得分:5)

对于任何数字 k ,您只需尝试使用素数除以√k进行除法。这是因为任何大于√k的素数都需要与√k的素数 相乘。

证明:

√k*√k= k 所以(a +√k)*√k&gt; k (对于所有 0&lt; a&lt;(k-√k))。由此得出(a +√k)除以√k之外还有 k

这通常用于加速寻找素数。

答案 1 :(得分:5)

当使用Eratosthenes算法的筛子从素数生成复合材料时,您不需要所有先前的素数,只需要当前生产点的平方根以下的素数就足够了。

这大大降低了内存要求。那么素数就是那些不在复合体中的奇数。

每个素数 p 产生一个多重链,从它的正方形开始,用 2p 的步骤枚举(因为我们只使用奇数)。这些具有其步长值的倍数存储在字典中,从而形成优先级队列。只有当前候选的平方根的素数才会出现在此优先级队列中(与E的分段筛相同的内存要求。)

符号上,SoE是

P = {3,5,7,9,...} \ U {{ p 2 ,p 2 + 2p,p 2 + 4p,p 2 + 6p, ...} | P }

中的 p

每个(奇数)素数生成其倍数的流;当所有这些流合并在一起时,我们拥有所有(奇数)复合数;如果没有复合材料(和2),素数都是可能的。

在Python中(希望可以读作可执行的伪代码),

def postponed_sieve():                   # postponed sieve, by Will Ness      
    yield 2; yield 3; yield 5; yield 7;  # original code David Eppstein, 
    D = {}                               #            ActiveState Recipe 2002
    ps = (p for p in postponed_sieve())  # a separate Primes Supply:
    p = ps.next() and ps.next()          # (3) a Prime to add to dict
    q = p*p                              # (9) when its sQuare is 
    c = 9                                # the next Candidate
    while True:
        if c not in D:                # not a multiple of any prime seen so far:
            if c < q: yield c         #   a prime, or
            else:   # (c==q):         #   the next prime's square:
                add(D,c + 2*p,2*p)    #     (9+6,6 : 15,21,27,33,...)
                p=ps.next()           #     (5)
                q=p*p                 #     (25)
        else:                         # 'c' is a composite:
            s = D.pop(c)              #   step of increment
            add(D,c + s,s)            #   next multiple, same step
        c += 2                        # next odd candidate

def add(D,x,s):                          # make no multiple keys in Dict
    while x in D: x += s                 # increment by the given step
    D[x] = s  

一旦产生素数,它就会被遗忘。一个单独的主要供应来自同一个生成器的单独调用,递归地,以维护字典。而那个的主要供应也是从另一个中提取的,也是递归的。每个只需要提供到其生产点的平方根,因此总体上需要很少的生成器,并且它们的大小渐近无关紧要(sqrt( sqrt( N))等)。

答案 2 :(得分:3)

我编写了一个程序,按顺序生成素数,没有限制,用它来汇总my blog的前十亿个素数。该算法使用分段的Eratosthenes筛;在每个区段计算额外的筛分质数,因此只要您有足够的空间存储筛分质数,该过程可以无限期地继续。这是伪代码:

function init(delta) # Sieve of Eratosthenes
  m, ps, qs := 0, [], []
  sieve := makeArray(2 * delta, True)
  for p from 2 to delta
    if sieve[p]
      m := m + 1; ps.insert(p)
      qs.insert(p + (p-1) / 2)
      for i from p+p to n step p
        sieve[i] := False
  return m, ps, qs, sieve

function advance(m, ps, qs, sieve, bottom, delta)
  for i from 0 to delta - 1
    sieve[i] := True
  for i from 0 to m - 1
    qs[i] := (qs[i] - delta) % ps[i]
  p := ps[0] + 2
  while p * p <= bottom + 2 * delta
    if isPrime(p) # trial division
      m := m + 1; ps.insert(p)
      qs.insert((p*p - bottom - 1) / 2)
    p := p + 2
  for i from 0 to m - 1
    for j from qs[i] to delta step ps[i]
      sieve[j] := False
  return m, ps, qs, sieve

此处ps是小于当前最大值的筛选素数列表,qs是当前段中相应ps的最小倍数的偏移量。 advance函数会清除位数,重置qs,使用新的筛选素数扩展psqs,然后筛选下一个段。

function genPrimes()
  bottom, i, delta := 0, 1, 50000
  m, ps, qs, sieve := init(delta)
  yield 2
  while True
    if i == delta # reset for next segment
      i, bottom := -1, bottom + 2 * delta
      m, ps, qs, sieve := \textbackslash
        advance(m, ps, qs, sieve, bottom, delta)
    else if sieve[i] # found prime
      yield bottom + 2*i + 1
    i := i + 1

段大小2 * delta任意设置为100000.此方法需要O(sqrt(n))空间用于筛分素数加上筛子的恒定空间。

速度较慢但节省空间以生成带轮子的候选人并测试候选人的素数。

function genPrimes()
  w, wheel := 0, [1,2,2,4,2,4,2,4,6,2,6,4,2,4, \
       6,6,2,6,4,2,6,4,6,8,4,2,4,2,4,8,6,4,6, \
       2,4,6,2,6,6,4,2,4,6,2,6,4,2,4,2,10,2,10]
  p := 2; yield p
  repeat
    p := p + wheel[w]
    if w == 51 then w := 4 else w := w + 1
    if isPrime(p) yield p

当筛子变得太大时,从筛子开始并切换到轮子可能是有用的。更好的是继续使用一些固定的筛选素数进行筛选,一旦集合变得太大,然后仅报告通过素性测试的那些值bottom + 2*i + 1