-
-
Notifications
You must be signed in to change notification settings - Fork 46.6k
/
Copy pathsol1.py
191 lines (140 loc) · 5.27 KB
/
sol1.py
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
"""
Project Euler Problem 187: https://projecteuler.net/problem=187
A composite is a number containing at least two prime factors.
For example, 15 = 3 x 5; 9 = 3 x 3; 12 = 2 x 2 x 3.
There are ten composites below thirty containing precisely two,
not necessarily distinct, prime factors: 4, 6, 9, 10, 14, 15, 21, 22, 25, 26.
How many composite integers, n < 10^8, have precisely two,
not necessarily distinct, prime factors?
"""
from math import isqrt
from maths.prime_sieve_eratosthenes import np_prime_sieve_eratosthenes
def slow_calculate_prime_numbers(max_number: int) -> list[int]:
"""
Returns prime numbers below max_number.
See: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
>>> slow_calculate_prime_numbers(10)
[2, 3, 5, 7]
>>> slow_calculate_prime_numbers(2)
[]
"""
# List containing a bool value for every number below max_number/2
is_prime = [True] * max_number
for i in range(2, isqrt(max_number - 1) + 1):
if is_prime[i]:
# Mark all multiple of i as not prime
for j in range(i**2, max_number, i):
is_prime[j] = False
return [i for i in range(2, max_number) if is_prime[i]]
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
>>> py_calculate_prime_numbers(10)
[2, 3, 5, 7]
>>> py_calculate_prime_numbers(2)
[]
"""
if max_number <= 2:
return []
# List containing a bool value for every odd number below max_number/2
is_prime = [True] * (max_number // 2)
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] * (
# Same as: (max_number - (i**2)) // (2 * i) + 1
# but faster than len(is_prime[i**2 // 2 :: i])
len(range(i**2 // 2, max_number // 2, i))
)
return [2] + [2 * i + 1 for i in range(1, max_number // 2) if is_prime[i]]
def slow_solution(max_number: int = 10**8) -> int:
"""
Returns the number of composite integers below max_number have precisely two,
not necessarily distinct, prime factors.
>>> slow_solution(30)
10
"""
prime_numbers = slow_calculate_prime_numbers(max_number // 2)
semiprimes_count = 0
left = 0
right = len(prime_numbers) - 1
while left <= right:
while prime_numbers[left] * prime_numbers[right] >= max_number:
right -= 1
semiprimes_count += right - left + 1
left += 1
return semiprimes_count
def while_solution(max_number: int = 10**8) -> int:
"""
Returns the number of composite integers below max_number have precisely two,
not necessarily distinct, prime factors.
>>> while_solution(30)
10
"""
prime_numbers = py_calculate_prime_numbers(max_number // 2)
semiprimes_count = 0
left = 0
right = len(prime_numbers) - 1
while left <= right:
while prime_numbers[left] * prime_numbers[right] >= max_number:
right -= 1
semiprimes_count += right - left + 1
left += 1
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,
not necessarily distinct, prime factors.
>>> solution(30)
10
"""
prime_numbers = np_prime_sieve_eratosthenes((max_number - 1) // 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 benchmark() -> None:
"""
Benchmarks
"""
# Running performance benchmarks...
# slow_solution : 108.50874730000032
# while_sol : 28.09581200000048
# 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)}")
if __name__ == "__main__":
print(f"Solution: {solution()}")
benchmark()