Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: 95% faster Project Euler 187 #10580

Closed
67 changes: 61 additions & 6 deletions project_euler/problem_187/sol1.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

from math import isqrt

import numpy as np


def slow_calculate_prime_numbers(max_number: int) -> list[int]:
"""
Expand All @@ -38,15 +40,15 @@ def slow_calculate_prime_numbers(max_number: int) -> list[int]:
return [i for i in range(2, max_number) if is_prime[i]]


def calculate_prime_numbers(max_number: int) -> list[int]:
def py_calculate_prime_numbers(max_number: int) -> list[int]:
"""
Returns prime numbers below max_number.
See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

>>> calculate_prime_numbers(10)
>>> py_calculate_prime_numbers(10)
[2, 3, 5, 7]

>>> calculate_prime_numbers(2)
>>> py_calculate_prime_numbers(2)
[]
"""

Expand All @@ -68,6 +70,32 @@ def calculate_prime_numbers(max_number: int) -> list[int]:
return [2] + [2 * i + 1 for i in range(1, max_number // 2) if is_prime[i]]


def np_calculate_prime_numbers(max_number: int) -> list[int]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to make this as a helper function in a common module as I see it being used in your other PR as well or maybe add it in maths/ and re-use it here or (assuming that there's already similar function in maths/) re-use an existing implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dhruvmanila,
Please see if I added the helper function correctly.

Also I found out that the benchmark in the file maths/prime_numbers.py was not working as expected, so I fixed it.

"""
Returns prime numbers below max_number.
See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

>>> np_calculate_prime_numbers(10)
[2, 3, 5, 7]
>>> np_calculate_prime_numbers(2)
[]
"""
if max_number <= 2:
return []

# List containing a bool value for every odd number below max_number/2
is_prime = np.ones(max_number // 2, dtype=bool)

for i in range(3, isqrt(max_number - 1) + 1, 2):
if is_prime[i // 2]:
# Mark all multiple of i as not prime using list slicing
is_prime[i**2 // 2 :: i] = False

primes = np.where(is_prime)[0] * 2 + 1
primes[0] = 2
return primes.tolist()


def slow_solution(max_number: int = 10**8) -> int:
"""
Returns the number of composite integers below max_number have precisely two,
Expand Down Expand Up @@ -100,7 +128,7 @@ def while_solution(max_number: int = 10**8) -> int:
10
"""

prime_numbers = calculate_prime_numbers(max_number // 2)
prime_numbers = py_calculate_prime_numbers(max_number // 2)

semiprimes_count = 0
left = 0
Expand All @@ -114,6 +142,31 @@ def while_solution(max_number: int = 10**8) -> int:
return semiprimes_count


def for_solution(max_number: int = 10**8) -> int:
"""
Returns the number of composite integers below max_number have precisely two,
not necessarily distinct, prime factors.

>>> for_solution(30)
10
"""

prime_numbers = py_calculate_prime_numbers(max_number // 2)

semiprimes_count = 0
right = len(prime_numbers) - 1
for left in range(len(prime_numbers)):
if left > right:
break
for r in range(right, left - 2, -1):
if prime_numbers[left] * prime_numbers[r] < max_number:
break
right = r
semiprimes_count += right - left + 1

return semiprimes_count


def solution(max_number: int = 10**8) -> int:
"""
Returns the number of composite integers below max_number have precisely two,
Expand All @@ -123,7 +176,7 @@ def solution(max_number: int = 10**8) -> int:
10
"""

prime_numbers = calculate_prime_numbers(max_number // 2)
prime_numbers = np_calculate_prime_numbers(max_number // 2)

semiprimes_count = 0
right = len(prime_numbers) - 1
Expand All @@ -146,14 +199,16 @@ def benchmark() -> None:
# Running performance benchmarks...
# slow_solution : 108.50874730000032
# while_sol : 28.09581200000048
# solution : 25.063097400000515
# for_sol : 25.063097400000515
# solution : 5.219610300000568

from timeit import timeit

print("Running performance benchmarks...")

print(f"slow_solution : {timeit('slow_solution()', globals=globals(), number=10)}")
print(f"while_sol : {timeit('while_solution()', globals=globals(), number=10)}")
print(f"for_sol : {timeit('for_solution()', globals=globals(), number=10)}")
print(f"solution : {timeit('solution()', globals=globals(), number=10)}")


Expand Down