改进python脚本涉及解决euler n°357

时间:2011-12-24 21:40:59

标签: python algorithm math

我知道这是一个奇怪而恼人的问题,但我希望圣诞气氛能让工作更轻松。 简而言之:我曾尝试编写一个旨在解决欧拉问题编号357的python脚本,其中说:“查找所有正整数n的总和不超过100000000,这样每个除数d nd+n/d是素数。“ 所以我编写了这个python脚本,我尝试多次改进,甚至在网上搜索。 最终我设法将它提高了十倍以上,但是即使在运行24小时之后它也无法通过100M数字(是的,我试过)。 所以这是剧本:我想知道是否有什么不对或是什么让它走得这么慢。忽略评论,因为其中一些是用我自己的语言。

#Find the sum of all positive integers n not exceeding 100000000
#such that for every divisor d of n, d+n/d is prime.

import time
maximum=100000000

def isprime(n):
    # range starts with 2 and only needs to go up the squareroot of n
    for x in range(2,int(n**0.5)+1):
        if n%x==0:
            return False
    return True

#main()
start=time.time()
final=list()
for n in range(1,maximum):
    counter,d=1,1
    divisors=list()
    while d<=(n**0.5): #Mettendo d<=n, e poi sotto range(len(divisori)/2) ci si mette il DECUPLO del tempo
        if n%d==0:
            divisors.append(d)
        d+=1
    for divisors_index in range(len(divisors)):
        prime=divisors[divisors_index]+n/divisors[divisors_index]
        if isprime(prime)==True:
            counter+=1
        elif isprime(prime)==False:
            counter=0
            break
    if counter==(len(divisors))+1:
        #print "%d:%s--->%d"%(n,divisors,len(divisors))
        final.append(n)   
end=time.time()-start

print "Results: %d. Time: %f seconds"%(len(final),end)

P.s:在网上我找到了Haskell的解决方案,然后移植到C(我知道了一点)。事实是,解决方案看起来与我的完全相同;无论如何我没有试着详细看它,因为我不想要剧透(实际上我现在想要它们)。 谢谢你,圣诞快乐。

6 个答案:

答案 0 :(得分:2)

不要建立一个除数列表然后再测试它们。在找到它们时对每个进行测试,并在找到无效数字后立即跳过该数字。

另一种表达问题的方法:“整数n的总和不超过100000000,这样就没有除数d,其中n / d + d是复合的”。我们可以在Python中更简单地表达这一点,首先创建一个函数来测试具有复合的除数,然后使用内置的函数编程工具。这通常比直接迭代列表更有效,并且还将自动实现“早期”逻辑(即,一旦我们找到具有复合和的除数就停止测试数字。)

import time
maximum = 100000000

def isprime(n):
    # We can use those same tools in the prime tester...
    return not any(n % x == 0 for x in range(2, int(n**0.5)+1))

def divisor_with_composite_sum(n, d):
    return n % d == 0 and not isprime(d + n / d)

start = time.time()
result = sum(
    n for n in range(1, maximum)
    if not any(
        divisor_with_composite_sum(n, d)
        for d in range(1, int(n**0.5) + 1)
    )
)

现在只是优化我们如何找到素数和除数:)


编辑:进一步思考。这是一个欧拉问题;你应该有比蛮力更聪明的东西。

每个符合条件的数字都会在其素数因子分解中具有幂1的每个素数。矛盾证明:假设某些素数pn可被p^2整除(或p的更高幂)。然后p可以被p整除,n/p可以被p整除(通过假设),因此总和可以被p整除,因此复合。此外,每个符合条件的数字都比素数少1,因为1总是一个除数。

所以:建立一个高达100000000的素数列表(例如,参见Amadan的答案,看看你是否也可以应用上述技术;))。对于其中的每一个,减去1,并检查结果是否具有重复的素因子。如果没有,那么你可以做更详细的测试。

答案 1 :(得分:1)

我怀疑你在isprime功能上花了很多时间。也许使用一些额外的内存,并使用主筛在列表中存储每个数字在开头是否为素数会有所帮助。

您甚至可以通过使用与筛子非常相似的算法同时生成素数列表和除数来进一步优化。

答案 2 :(得分:1)

我声称你在isprime方法上花了很多时间。在这方面,有两种可能的想法可以改进:

  • 生成一个(已排序的)素数列表(使用eratosthenes筛或其他东西)并查找数字而不是执行该循环来测试数字是否为素数。

  • 为isprime查找构建缓存:每当确定数字是否为素数时,将结果保存在list / dict / whatever中。在调用isprime之前,通过查看list / dict / whatever来检查你是否已经测试过该号码。

第二个建议不如第一个建议有效,但很容易修补到当前的解决方案。

答案 3 :(得分:1)

#!/usr/bin/env python                                                       

def primes(ubound):
    ubound = ubound + 1
    a = [False] * ubound
    p = 2
    primes = []
    while p < ubound:
        primes.append(p)
        for n in range(p, ubound, p):
            a[n] = True
        p = p + 1
        while p < ubound and a[p]:
            p = p + 1
    return primes

print primes(100000000)


$ time erastothenes.py > results.txt
real    1m32.441s
user    1m14.866s
sys     0m4.588s

它仍然有些迟钝,可能会进一步改善;我不是Python的专家。也许使用原生数组。我不知道如何在没有先制作清单的情况下制作一个,这是浪费时间。

(是的,我知道我在浪费前两个阵列位置。:p)

编辑通过算法优化@DanielFisher谈论(而不是浪费空间):

#!/usr/bin/env python                                                       

def primes(ubound):
    size = (ubound - 3) / 2
    a = [False] * size
    s = 0
    primes = []
    while s < size:
        t = 2 * s
        p = t + 3
        primes.append(p)
        print p
        for n in range(t * (s + 3) + 3, size, p):
            a[n] = True
        s = s + 1
        while s < size and a[s]:
            s = s + 1
    return primes

primes(100000000)

$ time erastothenes.py > results.txt
real    0m36.737s
user    0m35.725s
sys     0m0.864s

答案 4 :(得分:1)

你花了太多时间收集除数。

要查找除数,请检查每个不超过n**0.5的数字是否除以n。那就是sum([int(n**0.5) for n in range(1,maximum)]),大约6.7*10**11,分歧。这需要很多的时间。慢isprime函数是一个相对较小的问题,因为大多数时候它会很快找到候选复合。

使用看起来像相同算法的Haskell解决方案可以更快,因为Haskell是懒惰的。除数列表仅根据需要进行计算,因此在仅计算极少数除数后,大多数数字将被消除,这很快就完成了。在Python中,您可以使用generator(yield)来实现它。

为了获得更快的程序,JeffS同时筛选素数和除数的建议(筛子只有除数,素数/素数测试自动跟随)是一个很好的第一步。通过对问题进行一些数学分析可以获得更多。找到一些相对简单的必要条件的数字。你会发现没有太多的数字可以测试。

答案 5 :(得分:0)

您可以通过以下更改显着加快isPrime功能:

from itertools import count

def isPrime(n):
  # eliminate multiples of 2 and 3.
  if n % 2 == 0 or n % 3 == 0:
    return False

  limit = n ** 0.5

  for i in count(1):
    # all other primes take the form 6k +- 1.
    # though not all values of k produce a prime.
    high, low = 6*i + 1, 6*i - 1

    # we'll never find a factor greater than the square
    # root isn't composite and which would be ruled out
    # by earlier tests.
    if low > limit:
      return True

    # if we find a factor, it's obviously not prime.
    if n % low == 0:
      return False
    if n % high == 0:
      return False

通过缓存素数列表可以进一步改善这一点。筛子实际上更快(我相信),但这很简单,也很容易使用。