【问题标题】:Calculating the first triangle number to have over 500 divisors in python计算第一个三角形数以在 python 中有超过 500 个除数
【发布时间】:2013-03-22 00:44:11
【问题描述】:

我正在尝试解决 Project Euler 的第 12 个问题。我可以在将近 4 分钟内计算出超过 500 个除数的数字。我怎样才能让它更快?这是尝试;

import time

def main():
    memo={0:0,1:1}
    i=2
    n=200
    while(1):
        if len(getD(getT(i)))>n:
            break
        i+=1
    print(getT(i))

#returns the nth triangle number
def getT(n):
    if not n in memo:
        memo[n]=n+getT(n-1)
    return memo[n]

#returns the list of the divisors
def getD(n):
    divisors=[n]
    for i in xrange(1,int((n/2)+1)):
        if (n/float(i))%1==0:
            divisors.append(i)
    return divisors

startTime=time.time()
main()
print(time.time()-startTime)

【问题讨论】:

标签: python performance math


【解决方案1】:

使用装饰器(由 activestate recipes 提供)保存先前计算的值,并使用列表推导生成除数:

def memodict(f):
    """ Memoization decorator for a function taking a single argument """
    class memodict(dict):
        def __missing__(self, key):
            ret = self[key] = f(key)
            return ret 
    return memodict().__getitem__

@memodict
def trinumdiv(n):
    '''Return the number of divisors of the n-th triangle number'''
    numbers = range(1,n+1)
    total = sum(numbers)
    return len([j for j in range(1,total+1) if total % j == 0])

def main():
    nums = range(100000)
    for n in nums:
        if trinumdiv(n) > 200:
           print n
           break

结果:

In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:def main():
:       nums = range(10000)
:       for n in nums:
:               if trinumdiv(n) > 100:
:                  print 'Found:', n
:                  break
:
:startTime=time.time()
:main()
:print(time.time()-startTime)
:--
Found: 384
1.34229898453

In [2]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:def main():
:       nums = range(10000)
:       for n in nums:
:               if trinumdiv(n) > 200:
:                  print 'Found:', n
:                  break
:
:startTime=time.time()
:main()
:print(time.time()-startTime)
:--
Found: 2015
220.681169033

【讨论】:

    【解决方案2】:

    您不需要数组来存储三角形数字。您可以使用单个 int,因为您只检查一个值。此外,使用三角形数公式:n*(n+1)/2 可能会有所帮助,您可以在其中找到 nth 三角形数。

    getD 也只需要返回一个数字,因为您只是在寻找 500 个除数,而不是除数的值。

    但是,您真正的问题在于 for 循环中的 n/2。通过检查因子对,您可以使用sqrt(n)。所以只检查不超过sqrt(n) 的值。如果您检查到n/2,您会得到大量浪费的测试(以百万计)。

    所以你想做以下事情(n 是整数,以求除数,d 是可能的除数):

    • 确保n/d 没有余数。
    • 确定是在除数上加 1 还是 2。

    【讨论】:

    • 一个细节:如你所说,如果i < sqrt,其中sqrt = Math.sqrt(n)是一个因素,那么in/i都是因素。但是,如果sqrt 是一个因素,那只会在总数中增加一个,因为sqrt == n/sqrt。因此,因子总数是为i, 1 <= i <= n 找到的因子数的两倍,如果n == sqrt * sqrt 则减去一个。
    • 对不起,我不明白。例如:28 有以下除数:1,2,4,7,14,2828 的平方根是 5.2915026。所以,如果我数到平方根,我会错过7
    • @lorussian 4 * 7 = 28。因此,如果您检查 sqrt,您会发现 7。
    • @Justin 请详细说明您的评论。没有更多的上下文是没有意义的。他也会错过 14 岁。
    • 你找到14是因为你找到了2...所以在使用这种方法时你必须意识到因子的数量大约是一半。想象平方根除数在所有可能除数的中心,所以在这种情况下,它在 4 到 7 之间,
    【解决方案3】:

    几厘米。

    正如 Quincunx 所写,您只需要检查从 1..sqrt(n) 开始的整数范围,这将转化为类似 for i in xrange(1, sqrt(n) + 1): ... 的内容。仅此优化就可以大大加快速度。

    您可以使用三角形数公式(我直到现在才知道,谢谢 Quincunx),或者您可以使用另一种方法来查找三角形数,而不是递归和字典查找。您只需要序列中的下一个数字,因此保存它没有意义。函数调用在 Python 中涉及大量开销,因此通常不建议将递归用于数字运算。另外,为什么要投到float,我不太明白?

    我看到您已经在使用xrange 而不是range 来构建int 流。我假设您知道xrange 更快,因为它是作为生成器函数实现的。你也可以那样做。这也让事情变得更加顺利。

    我已经尝试这样做了,使用生成器,下面的代码在我的机器 (YMMV) 上找到了大约 16 秒内的第 500 个三角形数。但我还使用了一个巧妙的技巧来找到除数,即quadratic sieve

    这是我的代码:

    def triangle_num_generator():
        """ return the next triangle number on each call
            Nth triangle number is defined as SUM([1...N]) """
        n = 1
        s = 0
        while 1:
            s += n
            n += 1
            yield s
    
    
    def triangle_num_naive(n):
        """ return the nth triangle number using the triangle generator """
        tgen = triangle_num_generator()
        ret = 0
        for i in range(n):
            ret = tgen.next()
        return ret
    
    def divisor_gen(n):
        """ finds divisors by using a quadrativ sieve """
        divisors = []
        # search from 1..sqrt(n)
        for i in xrange(1, int(n**0.5) + 1):
            if n % i is 0:
                yield i
                if i is not n / i:
                    divisors.insert(0, n / i)
        for div in divisors:
            yield div
    
    
    def divisors(n):
        return [d for d in divisor_gen(n)]
    
    
    num_divs = 0
    i = 1
    while num_divs < 500:
        i += 1
        tnum = triangle_num_naive(i)
        divs = divisors(tnum)
        num_divs = len(divs)
    
    print tnum # 76576500
    

    在我简陋的机器上运行它会产生以下输出:

    morten@laptop:~/documents/project_euler$ time python pr012.py 
    76576500
    
    real    0m16.584s
    user    0m16.521s
    sys     0m0.016s
    

    使用三角形公式代替幼稚的方法:

    real    0m3.437s
    user    0m3.424s
    sys     0m0.000s
    

    【讨论】:

      【解决方案4】:

      我为同一任务编写了代码。它相当快。我使用了一种非常快速的因子查找算法来查找数字的因子。我还使用(n^2 + n)/2 来查找三角形数字。代码如下:

      from functools import reduce
      import time
      start = time.time()
      n = 1
      list_divs = []
      while len(list_divs) < 500:
          tri_n = (n*n+n)/2 # Generates the triangle number T(n)
          list_divs = list(set(reduce(list.__add__,([i, int(tri_n//i)] for i in range(1, int(pow(tri_n, 0.5) + 1)) if tri_n % i == 0)))) # this is the factor generator for any number n
          n+=1
      print(tri_n, time.time() - start)
      

      它在一台正常的计算机上在 15 秒内完成工作。

      【讨论】:

        【解决方案5】:

        这是该问题的另一种解决方案。在此,我使用 Eratosthenes 的筛子找到素数,然后进行素数分解。 应用以下公式计算一个数的因数: 因子总数=(n+1)*(m+1).....

        其中数字=2^n*3^n.......

        我的最佳时间是 1.9 秒。

        from time import time
        t=time()
        
        a=[0]*100
        c=0
        for i in range(2,100):
            if a[i]==0:
                for j in range(i*i,100,i):
                    continue
                a[c]=i
                c=c+1
        print(a)
        
        n=1
        ctr=0
        while(ctr<=1000):
            ctr=1
            triang=n*(n+1)/2
            x=triang
            i=0
            n=n+1
            while(a[i]<=x):
                b=1
                while(x%a[i]==0):
                    b=b+1
                    x=x//a[i];
                i=i+1
                ctr=ctr*b
        print(triang)
        print("took time",time()-t)
        

        【讨论】:

          【解决方案6】:

          这是我的答案,大约 3 秒即可解决。我认为可以通过跟踪除数或生成用作除数的素数列表来加快速度……但 3 秒对我来说已经足够快了。

          import time
          
          def numdivisors(triangle):
            factors = 0
            for i in range(1, int((triangle ** 0.5)) + 1):
              if triangle % i == 0:
                factors += 1
            return factors * 2
          
          def maxtriangledivisors(max):
            i = 1
            triangle = 0
            while i > 0:
              triangle += i
              if numdivisors(triangle) >= max:
                print 'it was found number', triangle,'triangle', i, 'with total of ', numdivisors(triangle), 'factors'
                return triangle
              i += 1
          
          startTime=time.time()
          maxtriangledivisors(500)
          print(time.time()-startTime)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-03-17
            • 1970-01-01
            • 2020-12-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多