我正在写一个递归无限素数生成器,我几乎可以肯定我可以更好地优化它。
现在,除了前十几个素数的查找表外,每次调用递归函数都会收到所有先前素数的列表。
因为它是一个懒惰的生成器,所以现在我只是过滤掉任何前一个素数的模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是作为匿名函数实现的,因此会破坏调用堆栈。
值得注意的是,我并不认为您需要所有先前的素数来生成下一个素数。由于我的实施,我碰巧碰到了它们。
答案 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
,使用新的筛选素数扩展ps
和qs
,然后筛选下一个段。
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
。