我对算法和运行时都很陌生,而且我正在尝试为个人项目优化一些代码。
import math
for num in range(0, 10000000000000000000000):
if all((num**(num+1)+(num+1)**(num))%i!=0 for i in range(2,int(math.sqrt((num**(num+1)+(num+1)**(num))))+1)):
print(num)
我该怎么做才能加快速度?我知道num=80
应该有用,但我的代码没有超过num=0, 1, 2
(它的速度不够快)。
首先我定义我的范围,然后我说如果这样的'是从范围2到sqrt(这样的)+ 1的素数,然后返回该数字。 Sqrt(n)+ 1是测试n的素数的最小因子数。
这是序列A051442
的素性测试答案 0 :(得分:2)
对于每次迭代而不是(num**(num+1)+(num+1)**(num))
次计算sqrt(num**(num+1)+(num+1)**(num))
,您可能只会获得一次轻微的提升。如您所见,这将大大降低您复杂性的常数因素。它不会改变基本的复杂性,因为你仍然需要计算余数。变化
if all((num**(num+1)+(num+1)**(num))%i!=0 for i in range(2,int(math.sqrt((num**(num+1)+(num+1)**(num))))+1)):
到
k = num**(num+1)+(num+1)**(num)
if all(k%i for i in range(2,int(math.sqrt(k))+1)):
!= 0
隐含在Python中。
<强>更新强>
所有这些对于极其低效的算法来说只是微不足道的改进。我能想到的最大加速是将支票k % i
简化为仅i
。对于i = a * b
这样的任何复合k % i == 0
,必须是k % a == 0 and k % b == 0
(如果k
可以被i
整除的情况,它也必须可以被i
整除因素sqrt(k)
)。
我假设您不想在代码中使用任何类型的预先计算的素数表。在这种情况下,您可以自己计算表格。这将涉及仅检查一次给定num
的所有数字,而不是每list
次迭代检查一次,因为我们可以将先前计算的素数存储为all
。这样可以有效地将当前k
范围的下限从2增加到前一个from math import sqrt
def extend(primes, from_, to):
"""
primes: a sequence containing prime numbers from 2 to `from - 1`, in order
from_: the number to start checking with
to: the number to end with (inclusive)
"""
if not primes:
primes.extend([2, 3])
return
for k in range(max(from_, 5), to + 1):
s = int(sqrt(k)) # No need to compute this more than once per k
for p in primes:
if p > s:
# Reached sqrt(k) -> short circuit success
primes.append(k)
break
elif not k % p:
# Found factor -> short circuit failure
break
的平方根。
让我们定义一个函数来使用Eratosthenes的seive来扩展我们的素数集:
k
现在我们可以使用这个函数在原始循环的每次迭代中扩展我们的素数列表。这允许我们仅针对缓慢增长的素数列表检查primes = []
prev = 0
for num in range(10000000000000000000000):
k = num**(num + 1) + (num + 1)**num
lim = int(sqrt(k)) + 1
extend(primes, prev, lim)
#print('Num={}, K={}, checking {}-{}, p={}'.format(num, k, prev, lim, primes), end='... ')
if k <= 3 and k in primes or all(k % i for i in primes):
print('{}: {} Prime!'.format(num, k))
else:
print('{}: {} Nope'.format(num, k))
prev = lim + 1
的可分性,而不是针对所有数字:
num == 13
我并不是100%确定我的扩展功能是最佳的,但是我可以在我那可笑的旧而慢的笔记本电脑上在<10分钟内到达k == 4731091158953433
,all(k % i for i in primes)
,所以我猜它不是太糟糕。这意味着该算法在此时构建了一个完整的素数表,最多可达~7e7。
更新#2
在调用extend
之前,您可以做的一种但并非真正优化的方法是检查primes = []
prev = 0
for num in range(10000000000000000000000):
k = num**(num + 1) + (num + 1)**num
lim = int(sqrt(k)) + 1
if not all(k % i for i in primes):
print('{}: {} Nope'.format(num, k))
continue
start = len(primes)
extend(primes, prev, lim)
if all(k % i for i in primes[start:]):
print('{}: {} Prime!'.format(num, k))
else:
print('{}: {} Nope'.format(num, k))
prev = lim + 1
。对于具有小素数因子的数字,这可以为你节省很多周期,但是当你最终必须计算所有素数达到一些巨大数字时,可能会在以后跟上你。以下是您如何做到这一点的示例:
num == 3
虽然这个版本从长远来看并没有太大作用,但它确实解释了为什么你能够在原始运行中如此快速地达到15。素数表确实在num == 16
之后延长,直到extend
,这也是此版本中发生可怕延迟的时间。两个版本中的净运行时间应该相同。
更新#3
正如@paxdiablo建议的那样,extend
中我们需要考虑的唯一数字是6 +/- 1的倍数。我们可以将它与通常只需要测试少量素数的事实相结合,并将from itertools import count
from math import ceil, sqrt
prime_table = [2, 3]
def prime_candidates(start=0):
"""
Infinite generator of prime number candidates starting with the
specified number.
Candidates are 2, 3 and all numbers that are of the form 6n-1 and 6n+1
"""
if start <= 3:
if start <= 2:
yield 2
yield 3
start = 5
delta = 2
else:
m = start % 6
if m < 2:
start += 1 - m
delta = 4
else:
start += 5 - m
delta = 2
while True:
yield start
start += delta
delta = 6 - delta
def isprime(n):
"""
Checks if `n` is prime.
All primes up to sqrt(n) are expected to already be present in
the generated `prime_table`.
"""
s = int(ceil(sqrt(n)))
for p in prime_table:
if p > s:
break
if not n % p:
return False
return True
def generate_primes(max):
"""
Generates primes up to the specified maximum.
First the existing table is yielded. Then, the new primes are
found in the sequence generated by `prime_candidates`. All verified
primes are added to the existing cache.
"""
for p in prime_table:
if p > max:
return
yield p
for k in prime_candidates(prime_table[-1] + 1):
if isprime(k):
prime_table.append(k)
if k > max:
# Putting the return here ensures that we always stop on a prime and therefore don't do any extra work
return
else:
yield k
for num in count():
k = num**(num + 1) + (num + 1)**num
lim = int(ceil(sqrt(k)))
b = all(k % i for i in generate_primes(lim))
print('n={}, k={} is {}prime'.format(num, k, '' if b else 'not '))
的功能转换为一个生成器,该生成器只计算绝对必要的素数。使用Python的懒惰生成应该会有所帮助。这是一个完全重写的版本:
k=343809097055019694337
此版本几乎立即达到15。它被卡在16,因为573645313
的最小素数因子是17
。未来的一些期望:
16248996011806421522977
应该轻而易举:19
有因素18
812362695653248917890473
需要一段时间:22156214713
有因子19
42832853457545958193355601
很简单:3
可被20
2375370429446951548637196401
也很简单:58967
可被21
138213776357206521921578463913
:13
可被22
8419259736788826438132968480177
:103
可被#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
typedef struct {
size_t alloc;
size_t size;
mpz_t *numbers;
} PrimeTable;
void init_table(PrimeTable *buf)
{
buf->alloc = 0x100000L;
buf->size = 2;
buf->numbers = malloc(buf->alloc * sizeof(mpz_t));
if(buf == NULL) {
fprintf(stderr, "No memory for prime table\n");
exit(1);
}
mpz_init_set_si(buf->numbers[0], 2);
mpz_init_set_si(buf->numbers[1], 3);
return;
}
void append_table(PrimeTable *buf, mpz_t number)
{
if(buf->size == buf->alloc) {
size_t new = 2 * buf->alloc;
mpz_t *tmp = realloc(buf->numbers, new * sizeof(mpz_t));
if(tmp == NULL) {
fprintf(stderr, "Ran out of memory for prime table\n");
exit(1);
}
buf->alloc = new;
buf->numbers = tmp;
}
mpz_set(buf->numbers[buf->size], number);
buf->size++;
return;
}
size_t print_table(PrimeTable *buf, FILE *file)
{
size_t i, n;
n = fprintf(file, "Array contents = [");
for(i = 0; i < buf->size; i++) {
n += mpz_out_str(file, 10, buf->numbers[i]);
if(i < buf->size - 1)
n += fprintf(file, ", ");
}
n += fprintf(file, "]\n");
return n;
}
void free_table(PrimeTable *buf)
{
for(buf->size--; ((signed)(buf->size)) >= 0; buf->size--)
mpz_clear(buf->numbers[buf->size]);
free(buf->numbers);
return;
}
int isprime(mpz_t num, PrimeTable *table)
{
mpz_t max, rem, next;
size_t i, d, r;
mpz_inits(max, rem, NULL);
mpz_sqrtrem(max, rem, num);
// Check if perfect square: definitely not prime
if(!mpz_cmp_si(rem, 0)) {
mpz_clears(rem, max, NULL);
return 0;
}
/* Normal table lookup */
for(i = 0; i < table->size; i++) {
// Got to sqrt(n) -> prime
if(mpz_cmp(max, table->numbers[i]) < 0) {
mpz_clears(rem, max, NULL);
return 1;
}
// Found a factor -> not prime
if(mpz_divisible_p(num, table->numbers[i])) {
mpz_clears(rem, max, NULL);
return 0;
}
}
/* Extend table and do lookup */
// Start with last found prime + 2
mpz_init_set(next, table->numbers[i - 1]);
mpz_add_ui(next, next, 2);
// Find nearest number of form 6n-1 or 6n+1
r = mpz_fdiv_ui(next, 6);
if(r < 2) {
mpz_add_ui(next, next, 1 - r);
d = 4;
} else {
mpz_add_ui(next, next, 5 - r);
d = 2;
}
// Step along numbers of form 6n-1/6n+1. Check each candidate for
// primality. Don't stop until next prime after sqrt(n) to avoid
// duplication.
for(;;) {
if(isprime(next, table)) {
append_table(table, next);
if(mpz_divisible_p(num, next)) {
mpz_clears(next, rem, max, NULL);
return 0;
}
if(mpz_cmp(max, next) <= 0) {
mpz_clears(next, rem, max, NULL);
return 1;
}
}
mpz_add_ui(next, next, d);
d = 6 - d;
}
// Return can only happen from within loop.
}
int main(int argc, char *argv[])
{
PrimeTable table;
mpz_t k, a, b;
size_t n, next;
int p;
init_table(&table);
mpz_inits(k, a, b, NULL);
for(n = 0; ; n = next) {
next = n + 1;
mpz_set_ui(a, n);
mpz_pow_ui(a, a, next);
mpz_set_ui(b, next);
mpz_pow_ui(b, b, n);
mpz_add(k, a, b);
p = isprime(k, &table);
printf("n=%ld k=", n);
mpz_out_str(stdout, 10, k);
printf(" p=%d\n", p);
//print_table(&table, stdout);
}
mpz_clears(b, a, k, NULL);
free_table(&table);
return 0;
}
因此,就即时满足而言,如果你能够超过18岁(比过去16岁要长100倍,在我的情况下需要大约1.25小时),这种方法会让你更进一步。
话虽如此,此时你最大的加速是用C或类似的低级语言重写它,这种语言没有那么多的循环开销。
更新#4
只是为了咯咯笑,这里是C语言中最新Python版本的实现。我选择使用GMP来获取任意精度整数,因为它易于使用并安装在我的Red Hat系统上,并且文档很清楚:
n == 18
虽然这个版本具有与Python版本完全相同的算法复杂度,但我认为它的运行速度要快几个数量级,因为在C中产生的开销相对较小。实际上,它需要大约15分钟才能卡住isprime
,比目前为止的Python版本快〜5倍。
更新#5
这将是最后一个,我保证。
GMP有一个名为mpz_nextprime
的函数,它可以更快地实现这个算法,特别是对于缓存。根据文件:
此函数使用概率算法来识别素数。实际上,它是足够的,复合传递的可能性非常小。
这意味着它可能比我实现的当前素数生成器快得多,并且在缓存中添加了一些虚假素数的略微成本偏移。这个成本应该是最小的:如果素数发生器比现在更快,即使添加几千个额外的模运算也应该没问题。
需要替换/修改的唯一部分是评论/* Extend table and do lookup */
下方mpz_nextprime
的部分。基本上整个部分只是对isprime
而不是递归的一系列调用。
此时,您可以在适当的时候调整mpz_probab_prime_p
以使用mpz_probab_prime_p
。您只需要检查int isprime(mpz_t num, PrimeTable *table)
{
mpz_t max, rem, next;
size_t i, r;
int status;
status = mpz_probab_prime_p(num, 50);
// Status = 2 -> definite yes, Status = 0 -> definite no
if(status != 1)
return status != 0;
mpz_inits(max, rem, NULL);
mpz_sqrtrem(max, rem, num);
// Check if perfect square: definitely not prime
if(!mpz_cmp_si(rem, 0)) {
mpz_clears(rem, max, NULL);
return 0;
}
mpz_clear(rem);
/* Normal table lookup */
for(i = 0; i < table->size; i++) {
// Got to sqrt(n) -> prime
if(mpz_cmp(max, table->numbers[i]) < 0) {
mpz_clear(max);
return 1;
}
// Found a factor -> not prime
if(mpz_divisible_p(num, table->numbers[i])) {
mpz_clear(max);
return 0;
}
}
/* Extend table and do lookup */
// Start with last found prime + 2
mpz_init_set(next, table->numbers[i - 1]);
mpz_add_ui(next, next, 2);
// Step along probable primes
for(;;) {
mpz_nextprime(next, next);
append_table(table, next);
if(mpz_divisible_p(num, next)) {
r = 0;
break;
}
if(mpz_cmp(max, next) <= 0) {
r = 1;
break;
}
}
mpz_clears(next, max, NULL);
return r;
}
的结果是否不确定:
n == 79
果然,这个版本最多只能在几秒钟内到达n == 80
。它似乎卡在mpz_probab_prime_p
上,可能是因为k
无法确定{{1}}是否是肯定的素数。我怀疑计算所有素数大约到10 ^ 80将需要花费大量时间。