Skip to content

Performance: 95% faster Project Euler 187 #10580

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

Closed
wants to merge 9 commits into from
6 changes: 3 additions & 3 deletions maths/prime_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ def benchmark():
from timeit import timeit

setup = "from __main__ import slow_primes, primes, fast_primes"
print(timeit("slow_primes(1_000_000_000_000)", setup=setup, number=1_000_000))
print(timeit("primes(1_000_000_000_000)", setup=setup, number=1_000_000))
print(timeit("fast_primes(1_000_000_000_000)", setup=setup, number=1_000_000))
print(timeit("list(slow_primes(1_000))", setup=setup, number=1_000))
print(timeit("list(primes(1_000))", setup=setup, number=1_000))
print(timeit("list(fast_primes(1_000))", setup=setup, number=1_000))


if __name__ == "__main__":
Expand Down
49 changes: 48 additions & 1 deletion maths/prime_sieve_eratosthenes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
"""

from math import isqrt

import numpy as np


def prime_sieve_eratosthenes(num: int) -> list[int]:
"""
Expand Down Expand Up @@ -45,10 +49,53 @@ def prime_sieve_eratosthenes(num: int) -> list[int]:
return [prime for prime in range(2, num + 1) if primes[prime]]


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

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

# List containing a bool value for every odd number below max_number/2
is_prime = np.ones((max_number + 1) // 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 benchmark():
"""
Benchmarks
"""
from timeit import timeit

print("Running performance benchmarks...")

functions = ["prime_sieve_eratosthenes", "np_prime_sieve_eratosthenes"]
for func in functions:
print(f"{func} : {timeit(f'{func}(10_000)', globals=globals(), number=10_000)}")


if __name__ == "__main__":
import doctest

doctest.testmod()

user_num = int(input("Enter a positive integer: ").strip())
print(prime_sieve_eratosthenes(user_num))
print(np_prime_sieve_eratosthenes(user_num))

benchmark()
41 changes: 35 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

from maths.prime_sieve_eratosthenes import np_prime_sieve_eratosthenes


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 Down Expand Up @@ -100,7 +102,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 +116,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 +150,7 @@ def solution(max_number: int = 10**8) -> int:
10
"""

prime_numbers = calculate_prime_numbers(max_number // 2)
prime_numbers = np_prime_sieve_eratosthenes((max_number - 1) // 2)

semiprimes_count = 0
right = len(prime_numbers) - 1
Expand All @@ -146,14 +173,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