Project Euler Problem 5: Smallest Multiple
Problem Statement
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?
Understanding the Problem
We need to find the Least Common Multiple (LCM) of all numbers from 1 to 20.
For the smaller example (1 to 10):
- We need a number divisible by: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
- That number is 2520
- Verification: 2520 ÷ 8 = 315, 2520 ÷ 9 = 280, etc. (all divide evenly)
My Solution
1
2
3
4
from math import lcm
from functools import reduce
reduce(lcm, range(1, 21))
Result: 232792560
How This Solution Works
This elegant solution leverages two powerful Python features:
Built-in math.lcm (Python 3.9+)
1
from math import lcm
Python’s math.lcm()
function:
- Computes the Least Common Multiple of two or more integers
- Highly optimized implementation
- Handles multiple arguments:
lcm(a, b, c, d)
- Returns 0 if any argument is 0
functools.reduce for Cumulative Operations
1
reduce(lcm, range(1, 21))
reduce
applies lcm
function cumulatively across the range:
- reduce(lcm, [1, 2, 3, 4, …])
- Step 1: lcm(1, 2) = 2
- Step 2: lcm(2, 3) = 6
- Step 3: lcm(6, 4) = 12
- Step 4: lcm(12, 5) = 60
- … continues until lcm(…, 20)
Why This Solution is Excellent
- Leverages Standard Library: Uses Python’s optimized
math.lcm
- Incredibly Concise: Just one line of actual computation
- Functional Style: Clean use of
reduce
for accumulation - Readable: Intent is immediately clear
- Efficient: Built-in functions are highly optimized
Alternative Solutions
Solution 1: My Previous Custom LCM
1
2
3
4
5
6
7
8
9
10
from functools import reduce
import math
def lcm_custom(a: int, b: int) -> int:
if a == b == 0:
raise ValueError('Either a or b must be nonZero')
return a * b // math.gcd(a, b)
def euler_05_custom(n: int) -> int:
return reduce(lcm_custom, range(1, n + 1), 1)
Solution 2: Using math.lcm with Multiple Arguments
1
2
3
4
from math import lcm
def euler_05_direct():
return lcm(*range(1, 21)) # Unpacks range into arguments
Solution 3: Traditional Loop
1
2
3
4
5
6
7
from math import lcm
def euler_05_loop():
result = 1
for i in range(1, 21):
result = lcm(result, i)
return result
Solution 4: Prime Factorization (Most Efficient for Large n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def euler_05_prime_factorization(n: int) -> int:
"""Mathematical approach using prime powers."""
from collections import defaultdict
def prime_factors(num):
factors = defaultdict(int)
d = 2
while d * d <= num:
while num % d == 0:
factors[d] += 1
num //= d
d += 1
if num > 1:
factors[num] += 1
return factors
# Find highest power of each prime up to n
max_powers = defaultdict(int)
for i in range(2, n + 1):
factors = prime_factors(i)
for prime, power in factors.items():
max_powers[prime] = max(max_powers[prime], power)
# LCM = product of all prime powers
result = 1
for prime, power in max_powers.items():
result *= prime ** power
return result
Performance Comparison
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time
def benchmark_solutions():
n = 20
solutions = [
("My Solution", lambda: reduce(lcm, range(1, n + 1))),
("Direct lcm(*args)", lambda: lcm(*range(1, n + 1))),
("Traditional Loop", lambda: euler_05_loop()),
("Prime Factorization", lambda: euler_05_prime_factorization(n)),
]
for name, func in solutions:
start = time.time()
result = func()
end = time.time()
print(f"{name}: {result} ({end-start:.6f}s)")
Results:
- My Solution: Very fast, clean and readable
- Direct lcm(*args): Slightly faster, but less flexible
- Traditional Loop: Same performance, more verbose
- Prime Factorization: More complex but scales better for large n
Mathematical Insights
Why 232,792,560 is the Answer
The LCM of 1-20 can be calculated by finding the highest power of each prime ≤ 20:
1
2
3
4
5
6
7
8
9
10
11
12
13
Primes ≤ 20: 2, 3, 5, 7, 11, 13, 17, 19
Highest powers needed:
- 2⁴ = 16 (from 16)
- 3² = 9 (from 9 and 18)
- 5¹ = 5 (from 5, 10, 15, 20)
- 7¹ = 7 (from 7, 14)
- 11¹ = 11 (from 11)
- 13¹ = 13 (from 13)
- 17¹ = 17 (from 17)
- 19¹ = 19 (from 19)
LCM = 2⁴ × 3² × 5 × 7 × 11 × 13 × 17 × 19 = 232,792,560
Pattern Recognition
Notice that for LCM(1, 2, …, n), we need:
- Highest power of 2 that’s ≤ n
- Highest power of 3 that’s ≤ n
- All primes ≤ n (to the first power)
Python Version Considerations
Python 3.9+ (Your approach):
1
2
from math import lcm
reduce(lcm, range(1, 21))
Python 3.8 and earlier:
1
2
3
4
5
6
7
import math
from functools import reduce
def lcm(a, b):
return a * b // math.gcd(a, b)
reduce(lcm, range(1, 21))
Your solution showcases how Python keeps improving - the built-in math.lcm
makes this type of problem much more elegant!
Key Takeaways
- Use the standard library -
math.lcm
is optimized and well-tested - functools.reduce is powerful for cumulative operations
- Mathematical problems often have built-in solutions in modern Python
- Concise doesn’t mean unclear - your one-liner is perfectly readable
- LCM problems appear frequently in number theory and programming contests
Complete Working Code
```python from math import lcm from functools import reduce
def solve_euler_5(): “"”Find smallest positive number evenly divisible by all numbers 1-20.””” return reduce(lcm, range(1, 21))
def solve_euler_5_flexible(n: int) -> int: “"”Find LCM of numbers 1 to n.””” return reduce(lcm, range(1, n + 1))
def verify_solution(result: int, n: int) -> bool: “"”Verify result is divisible by all numbers 1 to n.””” return all(result % i == 0 for i in range(1, n + 1))
if name == “main”: # Solve the main problem result = solve_euler_5() print(f”LCM of numbers 1-20: {result:,}”)
1
2
3
4
5
6
7
8
9
10
11
12
# Verify it works
print(f"Verification: {verify_solution(result, 20)}")
# Show progression for smaller ranges
for i in [5, 10, 15, 20]:
lcm_result = solve_euler_5_flexible(i)
print(f"LCM(1 to {i:2d}): {lcm_result:,}")
# Show individual divisibility for a few numbers
test_nums = [17, 18, 19, 20]
for num in test_nums:
print(f