优化Project Euler 12(Python)的解决方案

时间:2013-07-19 10:26:07

标签: python time long-integer execute

我有Project Euler Problem 12的以下代码。但是,执行需要很长时间。有没有人有加快速度的建议?

n = input("Enter number: ")
def genfact(n):
    t = []
    for i in xrange(1, n+1):
        if n%i == 0:
            t.append(i)
    return t

print "Numbers of divisors: ", len(genfact(n))
print

m = input("Enter the number of triangle numbers to check: ")
print
for i in xrange (2, m+2):
    a = sum(xrange(i))
    b = len(genfact(a))
    if b > 500:
        print a

对于n,我输入一个任意数字,例如6,只是为了检查它是否确实返回了因子数列表的长度。 对于m,我输入输入80 000 000

对于小数字,它的工作速度相对较快。如果我输入b > 50;它返回28表示a,这是正确的。

5 个答案:

答案 0 :(得分:3)

我的回答不是很漂亮或优雅,它仍然是蛮力。但是,它可以在一小段时间内简化问题空间并在不到10秒的时间内成功终止。

获取n:的因素 与@usethedeathstar提到的一样,可以测试最多n/2的因子。但是,我们可以通过仅测试n:

的平方根来做得更好
let n = 36
=> factors(n) : (1x36, 2x18, 3x12, 4x9, 6x6, 9x4, 12x3, 18x2, 36x1)

正如你所看到的,它在6之后循环(36的平方根)。我们也不需要明确地返回因子,只需找出有多少...所以只需用sum()中的生成器计算它们:

import math

def get_factors(n):
    return sum(2 for i in range(1, round(math.sqrt(n)+1)) if not n % i)

测试三角数

我使用了生成器函数来产生三角形数字:

def generate_triangles(limit):
    l = 1
    while l <= limit:
        yield sum(range(l + 1))
        l += 1

最后,开始测试:

def test_triangles():
    triangles = generate_triangles(100000)
    for i in triangles:
        if get_factors(i) > 499:
            return i

使用探查器运行它,它在不到10秒的时间内完成:

$ python3 -m cProfile euler12.py 

361986 function calls in 8.006 seconds

此处节省的最大时间是get_factors(n)仅测试n的平方根 - 这使得heeeaps更快,并且通过不生成因子列表来节省大量内存开销。

正如我所说,它仍然不漂亮 - 我相信有更优雅的解决方案。但是,它符合更快的要求:)

答案 1 :(得分:2)

我得到的答案是要在1.8秒内使用Python运行。

import time
from math import sqrt


def count_divisors(n):
    d = {}
    count = 1

    while n % 2 == 0:
        n = n / 2
        try:
            d[2] += 1
        except KeyError:
            d[2] = 1

    for i in range(3, int(sqrt(n+1)), 2):
        while n % i == 0 and i != n:
            n = n / i
            try:
                d[i] += 1
            except KeyError:
                d[i] = 1

    d[n] = 1

    for _,v in d.items():
        count = count * (v + 1)

    return count

def tri_number(num):
  next = 1 + int(sqrt(1+(8 * num)))
  return num + (next/2)


def main():
    i = 1
    while count_divisors(i) < 500:
        i = tri_number(i)
    return i


start = time.time()
answer = main()
elapsed = (time.time() - start)

print("result %s returned in %s seconds." % (answer, elapsed))

以下是显示时间增量和正确答案的输出:

$ python ./project012.py
result 76576500 returned in 1.82238006592 seconds.

因素

为计算除数,我首先初始化一个空字典和一个计数器。对于找到的每个因子,如果值不存在,则创建d [factor]的键,其值为1;否则,将增加d [factor]的值。

  

例如,如果我们计算因子100,我们将看到d = {25:1,2:2}

第一个while循环,我分解出所有2,每次将n除以2。接下来,我从3开始分解,每次跳过两次(因为我们已经分解了所有偶数),并且一旦到达n + 1的平方根就停止。

  

我们在n的平方根处停止,因为如果存在一个对数,其中一个数大于n的平方根,则对中的另一个必须小于10。如果不存在较小的数,则存在没有匹配的较大因素。   https://math.stackexchange.com/questions/1343171/why-only-square-root-approach-to-check-number-is-prime

while n % 2 == 0:
        n = n / 2
        try:
            d[2] += 1
        except KeyError:
            d[2] = 1

    for i in range(3, int(sqrt(n+1)), 2):
        while n % i == 0 and i != n:
            n = n / i
            try:
                d[i] += 1
            except KeyError:
                d[i] = 1
d[n] = 1

现在我已经获得了每个因子,并将其添加到字典中,我们必须添加最后一个因子(仅为n)。


计数因子

现在字典已完成,我们遍历每个项目,然后应用以下公式:d(n)=(a + 1)(b + 1)(c + 1)... https://www.wikihow.com/Determine-the-Number-of-Divisors-of-an-Integer

  

此公式的全部含义是将每个因子的所有计数取1,然后将它们相乘。以100为例,它具有因子25、2和2。我们将计算d(n)=(a + 1)(b + 1)=(1 + 1)(2 + 1)=(2)(3 )= 6个除数

for _,v in d.items():
    count = count * (v + 1)

return count

计算三角数

现在,看一下tri_number(),您可以看到我选择了计算序列中的下一个三角形数,而无需手动将每个整数相加(节省了数百万次操作)。取而代之的是我使用T(n)= n(n + 1)/ 2 http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/runsums/triNbProof.html

  

我们正在为函数提供一个整数作为参数,因此我们需要求解n,这将是接下来要添加的整数。一旦有了下一个数字(n),我们只需将单个数字添加到num并返回

     

S = n(n + 1)2

     

S = n2 + n2

     

2S = n2 + n

     

n2 + n−2S = 0

     

这时,我们对ax2 + bx + c = 0使用二次公式。

     

n = −b±√b2-4ac/ 2a

     

n = −1±√1-4(1)(− 2S)/ 2

     

n = −1±√1+ 8S / 2

     

https://socratic.org/questions/how-do-you-solve-for-n-in-s-n-n-1-2

因此,所有tri_number()所做的运算均为n = 1 +√1+ 8S / 2(我们在这里忽略了否定方程)。返回的答案是序列中的下一个三角形。

def tri_number(num):
  next = 1 + int(sqrt(1+(8 * num)))
  return num + (next/2)

主循环

最后,我们可以看一下main()。我们从整数1开始。我们计算1的除数。如果它小于500,我们得到下一个三角形数,然后一次又一次尝试,直到得到大于500的数。

def main():
    i = 1
    while count_divisors(i) < 500:
        i = tri_number(i)
    return i

我确信还有其他优化方法,但我不够聪明,无法理解这些方法。如果您找到优化python的更好方法,请告诉我!我最初在Golang解决了第12个项目,并且运行时间为25毫秒!

$ go run project012.go
76576500
2018/07/12 01:56:31 TIME: main() took 23.581558ms

答案 2 :(得分:0)

我可以给出的一个提示是

def genfact(n):
    t = []
    for i in xrange(1, n+1):
        if n%i == 0:
            t.append(i)
    return t

将其更改为

def genfact(n):
    t=[]
    for i in xrange(1,numpy.sqrt(n)+1):
        if(n%i==0):
           t.append(i)
           t.apend(n/i)

因为如果a是除数而不是b = n / a,因为a * b = a * n / b = n,那应该有助于一部分(不确定在你的情况下是否可能是正方形,但是如果是这样,添加另一个案例以排除两次添加相同的数字)

你也可以设计一个递归的东西,(比如它是28之类的东西,你得到1,28,2,14,而在你知道14的那一刻,你输入一些东西来实际记住除数14(memoize),而不是检查它们是否在列表中是alraedy,如果没有,则将它们添加到列表中,同时为每个14的除数加上28 / d,最后只取出重复项

如果您认为我的第一个答案仍然不够快,请求更多,我将检查如何通过一些更多的技巧更快地解决它(可能会使用erastothenes筛子等等,以及一些如果你想把这个问题真的炸成很大的比例,比如用超过10k的除数检查第一个,那么也可以考虑其他技巧。

答案 3 :(得分:0)

while True:
    c=0
    n=1
    m=1
    for i in range(1,n+1):
        if n%i==0:
            c=c+1
            m=m+1    
            n=m*(m+1)/2
            if c>500:
                break
print n

答案 4 :(得分:0)

这不是我的代码,但是已经过优化。 来源:http://code.jasonbhill.com/sage/project-euler-problem-12/

import time


def num_divisors(n):
    if n % 2 == 0: n = n / 2
    divisors = 1
    count = 0
    while n % 2 == 0:
        count += 1
        n = n / 2
    divisors = divisors * (count + 1)
    p = 3
    while n != 1:
        count = 0
        while n % p == 0:
            count += 1
            n = n / p
        divisors = divisors * (count + 1)
        p += 2
    return divisors


def find_triangular_index(factor_limit):
    n = 1
    lnum, rnum = num_divisors(n), num_divisors(n + 1)
    while lnum * rnum < 500:
        n += 1
        lnum, rnum = rnum, num_divisors(n + 1)
    return n


start = time.time()
index = find_triangular_index(500)
triangle = (index * (index + 1)) / 2
elapsed = (time.time() - start)

print("result %s returned in %s seconds." % (triangle, elapsed))