Skip to content

Commit 1ebae5d

Browse files
Performance: 75% faster Project Euler 187 (#10503)
* Add comments and wikipedia link in calculate_prime_numbers * Add improved calculate_prime_numbers * Separate slow_solution and new_solution * Use for loops in solution * Separate while_solution and new solution * Add performance benchmark * Add doctest for calculate_prime_numbers * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed white space --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 7dbc301 commit 1ebae5d

File tree

1 file changed

+111
-7
lines changed

1 file changed

+111
-7
lines changed

Diff for: project_euler/problem_187/sol1.py

+111-7
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,89 @@
1414
from math import isqrt
1515

1616

17-
def calculate_prime_numbers(max_number: int) -> list[int]:
17+
def slow_calculate_prime_numbers(max_number: int) -> list[int]:
1818
"""
19-
Returns prime numbers below max_number
19+
Returns prime numbers below max_number.
20+
See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
2021
21-
>>> calculate_prime_numbers(10)
22+
>>> slow_calculate_prime_numbers(10)
2223
[2, 3, 5, 7]
24+
25+
>>> slow_calculate_prime_numbers(2)
26+
[]
2327
"""
2428

29+
# List containing a bool value for every number below max_number/2
2530
is_prime = [True] * max_number
31+
2632
for i in range(2, isqrt(max_number - 1) + 1):
2733
if is_prime[i]:
34+
# Mark all multiple of i as not prime
2835
for j in range(i**2, max_number, i):
2936
is_prime[j] = False
3037

3138
return [i for i in range(2, max_number) if is_prime[i]]
3239

3340

34-
def solution(max_number: int = 10**8) -> int:
41+
def calculate_prime_numbers(max_number: int) -> list[int]:
42+
"""
43+
Returns prime numbers below max_number.
44+
See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
45+
46+
>>> calculate_prime_numbers(10)
47+
[2, 3, 5, 7]
48+
49+
>>> calculate_prime_numbers(2)
50+
[]
51+
"""
52+
53+
if max_number <= 2:
54+
return []
55+
56+
# List containing a bool value for every odd number below max_number/2
57+
is_prime = [True] * (max_number // 2)
58+
59+
for i in range(3, isqrt(max_number - 1) + 1, 2):
60+
if is_prime[i // 2]:
61+
# Mark all multiple of i as not prime using list slicing
62+
is_prime[i**2 // 2 :: i] = [False] * (
63+
# Same as: (max_number - (i**2)) // (2 * i) + 1
64+
# but faster than len(is_prime[i**2 // 2 :: i])
65+
len(range(i**2 // 2, max_number // 2, i))
66+
)
67+
68+
return [2] + [2 * i + 1 for i in range(1, max_number // 2) if is_prime[i]]
69+
70+
71+
def slow_solution(max_number: int = 10**8) -> int:
3572
"""
3673
Returns the number of composite integers below max_number have precisely two,
37-
not necessarily distinct, prime factors
74+
not necessarily distinct, prime factors.
3875
39-
>>> solution(30)
76+
>>> slow_solution(30)
77+
10
78+
"""
79+
80+
prime_numbers = slow_calculate_prime_numbers(max_number // 2)
81+
82+
semiprimes_count = 0
83+
left = 0
84+
right = len(prime_numbers) - 1
85+
while left <= right:
86+
while prime_numbers[left] * prime_numbers[right] >= max_number:
87+
right -= 1
88+
semiprimes_count += right - left + 1
89+
left += 1
90+
91+
return semiprimes_count
92+
93+
94+
def while_solution(max_number: int = 10**8) -> int:
95+
"""
96+
Returns the number of composite integers below max_number have precisely two,
97+
not necessarily distinct, prime factors.
98+
99+
>>> while_solution(30)
40100
10
41101
"""
42102

@@ -54,5 +114,49 @@ def solution(max_number: int = 10**8) -> int:
54114
return semiprimes_count
55115

56116

117+
def solution(max_number: int = 10**8) -> int:
118+
"""
119+
Returns the number of composite integers below max_number have precisely two,
120+
not necessarily distinct, prime factors.
121+
122+
>>> solution(30)
123+
10
124+
"""
125+
126+
prime_numbers = calculate_prime_numbers(max_number // 2)
127+
128+
semiprimes_count = 0
129+
right = len(prime_numbers) - 1
130+
for left in range(len(prime_numbers)):
131+
if left > right:
132+
break
133+
for r in range(right, left - 2, -1):
134+
if prime_numbers[left] * prime_numbers[r] < max_number:
135+
break
136+
right = r
137+
semiprimes_count += right - left + 1
138+
139+
return semiprimes_count
140+
141+
142+
def benchmark() -> None:
143+
"""
144+
Benchmarks
145+
"""
146+
# Running performance benchmarks...
147+
# slow_solution : 108.50874730000032
148+
# while_sol : 28.09581200000048
149+
# solution : 25.063097400000515
150+
151+
from timeit import timeit
152+
153+
print("Running performance benchmarks...")
154+
155+
print(f"slow_solution : {timeit('slow_solution()', globals=globals(), number=10)}")
156+
print(f"while_sol : {timeit('while_solution()', globals=globals(), number=10)}")
157+
print(f"solution : {timeit('solution()', globals=globals(), number=10)}")
158+
159+
57160
if __name__ == "__main__":
58-
print(f"{solution() = }")
161+
print(f"Solution: {solution()}")
162+
benchmark()

0 commit comments

Comments
 (0)