【发布时间】:2014-01-06 14:08:26
【问题描述】:
我正在编写一个递归无限素数生成器,我几乎可以肯定我可以更好地优化它。
现在,除了前十几个素数的查找表之外,对递归函数的每次调用都会收到所有先前素数的列表。
因为它是一个惰性生成器,所以现在我只是过滤掉之前任何素数的模 0 的任何数字,并获取第一个未过滤的结果。 (我正在使用短路的检查,因此前一个素数第一次将当前数字平均除以该信息会中止。)
现在,在搜索第 400 个素数 (37,813) 时,我的性能会下降。我正在寻找方法来使用我拥有所有先验素数列表的独特事实,并且只搜索下一个,以改进我的过滤算法。 (我能找到的大多数信息都提供了非惰性筛子来查找限制下的素数,或者在给定 pn-1 的情况下找到第 pn 个素数的方法,而不是优化给定 2...pn-1 个素数,求 pn。)
例如,我知道第 pn 个素数必须位于 (pn-1 + 1)...(pn -1+pn-2)。现在我开始在 pn-1 + 2 处过滤整数(因为 pn-1 + 1 只能是 pn-1 = 2,这是预先计算的)。但由于这是一个惰性生成器,因此知道范围的终端边界 (pn-1+pn-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) 从 Stream 中发出它,2) 将它添加到用于下一个电话。 (我确信这个堆栈是一些减速的根源。)
next_primes 函数接收该堆栈。从最后一个已知的素数 +2 开始,它创建一个无限的整数流,并丢弃每个整数除以列表的任何已知素数,然后返回第一个出现的整数。
我想,这类似于惰性增量 Eratosthenes 的筛子。
您可以看到一些基本的优化尝试:我从 pn-1+2 开始检查,然后跨过偶数。
我尝试了一个更逐字的 Eratosthenes 筛子,方法是将 Integer.stream 传递给每个计算,并在找到一个素数后,将 Integer.stream 包装在一个新的 Stream.drop_while 中,该 Stream.drop_while 只过滤掉该素数的倍数。但由于 Streams 是作为匿名函数实现的,因此破坏了调用堆栈。
值得注意的是,我并不是假设您需要所有先前的素数来生成下一个素数。多亏了我的实现,我碰巧有它们。
【问题讨论】:
-
你的问题不清楚。你想一直生成质数直到永恒吗?如果你发布一些代码,它可能会澄清一点。
-
“陷入困境”是什么意思?我刚刚在 C# 中编写了一个小的素数生成器,它在大约 1.8 毫秒内生成前 2500 个素数。全部的。它在大约 150 毫秒内生成高达一百万的所有素数。而且这段代码没有高度优化。我怀疑你的实现不是应该的。向我们展示您的代码。否则我们真的帮不了你。
-
@JimMischel BTW 例如this simple C++ code 在 Ideone 上运行得更快,需要 60 毫秒才能达到 1550 万(100 万素数);对于最多 100 万个用户来说,这大约是 4 毫秒(对于 Ideone 来说太小而无法测量)。但也许这是因为 C# 通常比 C++ 慢?从您的时间来看,您的代码或多或少都很好,logBase (78500 / 2500) (150/1.8) = 1.28,它运行在 ~ n^1.28,在产生的
n素数中(尽管真正的筛子应该运行在 ~ n ^1.1..1.05)。 n^1.28 是一半(也许在 2500 个素数的测量中有太多的噪音;如果是 2.8 毫秒,那将是 ~ n^1.15,~OK)。 -
@JimMischel 55,555 !!!!!! :)
标签: algorithm generator primes lazy-evaluation sieve-of-eratosthenes