Project Euler Problem 5 in Python – How can I optimize my solution?

Question:

I’ve currently been working on Project Euler problem #5 in Python.

The problem is "2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?"

I solved the problem by writing the following code:

d = 0 
z = 0 
while z < 20:
    d += 1  
    z = 0
    for i in range(1,21):
        if d % i != 0:
            z = 0 
        else: 
            z += 1 
print(d)

However, my code takes very long to run. I’d like to optimize my code to make it run faster.

Asked By: IbaD6

||

Answers:

Inspired by earlier comments, you could try the math gcd:


from math import gcd

N = 20
ans = 1
for x in range(2, N+1):
    ans = ans * x // gcd(ans, x)

    
print(ans)
232792560
Answered By: Daniel Hao

How can I optimize my solution?

You are looking for the solution iterating over all possible candidate values checking each time the entire condition for every candidate. You can optimize it by using
already known result for 1 to 10 (2520) and breaking the for i loop if it is obvious that the condition is not satisfied:

d = 2520
z = 0 
while z < 10:
    d += 2520
    z = 0
    for i in range(11, 21):
        if d % i != 0:
            break 
        else: 
            z += 1
print(d)

This way you cut down the computation time and are able to achieve a result.

A much better approach is to go the other way around not looking for a number fulfilling the condition but constructing this number starting from the obvious fact that the multiplication of all numbers up to 20 is divisible by all the numbers and then eliminate duplicate sub-factors iterating only over a range of the values for the required divisors:

factors = list(range(2,21))
max_indx = len(factors)-1
print('# 0 >', factors )
for curr_indx in range(max_indx):
    num = factors[curr_indx]
    print('#', num, end=' > ')
    for indx, item in enumerate(factors[curr_indx+1:]):
        if item >= num and not item%num: 
            factors[indx+curr_indx+1] = item//num
    print(factors)
import math
res = math.prod(factors)
print(res)
print(set(factors))

In the output of the code above you can see what the code does to get a list of not redundant factors for final multiplication to achieve the result:

# 0 > [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
# 2 > [2, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10]
# 3 > [2, 3, 2, 5, 1, 7, 4, 3, 5, 11, 2, 13, 7, 5, 8, 17, 3, 19, 10]
# 2 > [2, 3, 2, 5, 1, 7, 2, 3, 5, 11, 1, 13, 7, 5, 4, 17, 3, 19, 5]
# 5 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 7, 1, 4, 17, 3, 19, 1]
# 1 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 7, 1, 4, 17, 3, 19, 1]
# 7 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 4, 17, 3, 19, 1]
# 2 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 3, 19, 1]
# 3 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 1 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 11 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 1 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 13 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 1 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 1 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 2 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 17 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 1 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
# 19 > [2, 3, 2, 5, 1, 7, 2, 3, 1, 11, 1, 13, 1, 1, 2, 17, 1, 19, 1]
232792560
{1, 2, 3, 5, 7, 11, 13, 17, 19}

There is still room for optimizing the code above skipping unnecessary loop runs by adding a simple condition to the loop, so feel free to mention it in the comments demonstrating understanding of the way the code works.

The code above runs approximately from five to ten times slower than the code using gcd() (see further down) to construct the result, but it provides as a side-effect also all of the prime numbers you can get creating a set out of the list of factors.



Below other approaches provided here on stackoverflow as answers or comments :

Updated to Python 3.9 from answer of Project Euler #5 using python ( Just remember LCM(a, b, c) = LCM(a, LCM(b, c)). ) :

from math import gcd
from functools import reduce
print(reduce(lambda a, b: a * b // gcd(a, b), range(11, 21)))
# ---------------------- or without reduce: 
ans = 1
for i in range(11, 21):
    ans = ans * i // gcd(ans, i)
print(ans)

The for i loop seems to be slightly faster than reduce for small values like 20.

Since Python 3.8 there is also math.prod() available, so
if you reason it out knowing what LCM means and how it is calculated: 2 ** 4 (because 16 is <= 20) * 3 ** 2 (because 9 is <= 20) * 5 * 7 * 11 * 13 * 17 * 19 (all the other primes <= 20) = 232,792,560. ( see comment by Mark Tolonen ):

import math
print( math.prod([16, 9, 5, 7, 11, 13, 17, 19]) )

And if you want to write your own gcd() function, it works like this:
(https://en.wikipedia.org/wiki/Euclidean_algorithm):

def gcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a
Answered By: Claudio
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.