pythontimelong-integerexecute

Optimise the solution to Project Euler 12 (Python)


I have the following code for Project Euler Problem 12. However, it takes a very long time to execute. Does anyone have any suggestions for speeding it up?

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

For n, I enter an arbitrary number such as 6 just to check whether it indeed returns the length of the list of the number of factors. For m, I enter entered 80 000 000

It works relatively quickly for small numbers. If I enter b > 50 ; it returns 28 for a, which is correct.


Solution

  • My answer here isn't pretty or elegant, it is still brute force. But, it simplifies the problem space a little and terminates successfully in less than 10 seconds.

    Getting factors of n: Like @usethedeathstar mentioned, it is possible to test for factors only up to n/2. However, we can do better by testing only up to the square root of n:

    let n = 36
    => factors(n) : (1x36, 2x18, 3x12, 4x9, 6x6, 9x4, 12x3, 18x2, 36x1)
    

    As you can see, it loops around after 6 (the square root of 36). We also don't need to explicitly return the factors, just find out how many there are... so just count them off with a generator inside of sum():

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

    Testing the triangular numbers

    I have used a generator function to yield the triangular numbers:

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

    And finally, start testing:

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

    Running this with the profiler, it completes in less than 10 seconds:

    $ python3 -m cProfile euler12.py 
    
    361986 function calls in 8.006 seconds
    

    The BIGGEST time saving here is get_factors(n) testing only up to the square root of n - this makes it heeeaps quicker and you save heaps of memory overhead by not generating a list of factors.

    As I said, it still isn't pretty - I am sure there are more elegant solutions. But, it fits the bill of being faster :)