优化素性测试

时间:2018-01-05 04:43:42

标签: python-3.x optimization

我对算法和运行时都很陌生,而且我正在尝试为个人项目优化一些代码。

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

的素性测试

1 个答案:

答案 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 == 4731091158953433all(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
  • 整除
  • 13821377635720652192157846391313可被22
  • 整除
  • 8419259736788826438132968480177103可被#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; }
  • 整除
  • 等...(link to sequence

因此,就即时满足而言,如果你能够超过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将需要花费大量时间。