Skip to content

Commit defc205

Browse files
MaximSmolskiygithub-actions
and
github-actions
authored
perf: improve Project Euler problem 203 solution 1 (TheAlgorithms#6279)
Improve solution (locally 1500+ times - from 3+ seconds to ~2 milliseconds) Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
1 parent 97f25d4 commit defc205

File tree

1 file changed

+21
-91
lines changed

1 file changed

+21
-91
lines changed

project_euler/problem_203/sol1.py

+21-91
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
"""
3030
from __future__ import annotations
3131

32-
import math
33-
3432

3533
def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]:
3634
"""
@@ -61,76 +59,9 @@ def get_pascal_triangle_unique_coefficients(depth: int) -> set[int]:
6159
return coefficients
6260

6361

64-
def get_primes_squared(max_number: int) -> list[int]:
65-
"""
66-
Calculates all primes between 2 and round(sqrt(max_number)) and returns
67-
them squared up.
68-
69-
>>> get_primes_squared(2)
70-
[]
71-
>>> get_primes_squared(4)
72-
[4]
73-
>>> get_primes_squared(10)
74-
[4, 9]
75-
>>> get_primes_squared(100)
76-
[4, 9, 25, 49]
77-
"""
78-
max_prime = math.isqrt(max_number)
79-
non_primes = [False] * (max_prime + 1)
80-
primes = []
81-
for num in range(2, max_prime + 1):
82-
if non_primes[num]:
83-
continue
84-
85-
for num_counter in range(num**2, max_prime + 1, num):
86-
non_primes[num_counter] = True
87-
88-
primes.append(num**2)
89-
return primes
90-
91-
92-
def get_squared_primes_to_use(
93-
num_to_look: int, squared_primes: list[int], previous_index: int
94-
) -> int:
95-
"""
96-
Returns an int indicating the last index on which squares of primes
97-
in primes are lower than num_to_look.
98-
99-
This method supposes that squared_primes is sorted in ascending order and that
100-
each num_to_look is provided in ascending order as well. Under these
101-
assumptions, it needs a previous_index parameter that tells what was
102-
the index returned by the method for the previous num_to_look.
103-
104-
If all the elements in squared_primes are greater than num_to_look, then the
105-
method returns -1.
106-
107-
>>> get_squared_primes_to_use(1, [4, 9, 16, 25], 0)
108-
-1
109-
>>> get_squared_primes_to_use(4, [4, 9, 16, 25], 0)
110-
1
111-
>>> get_squared_primes_to_use(16, [4, 9, 16, 25], 1)
112-
3
62+
def get_squarefrees(unique_coefficients: set[int]) -> set[int]:
11363
"""
114-
idx = max(previous_index, 0)
115-
116-
while idx < len(squared_primes) and squared_primes[idx] <= num_to_look:
117-
idx += 1
118-
119-
if idx == 0 and squared_primes[idx] > num_to_look:
120-
return -1
121-
122-
if idx == len(squared_primes) and squared_primes[-1] > num_to_look:
123-
return -1
124-
125-
return idx
126-
127-
128-
def get_squarefree(
129-
unique_coefficients: set[int], squared_primes: list[int]
130-
) -> set[int]:
131-
"""
132-
Calculates the squarefree numbers inside unique_coefficients given a
133-
list of square of primes.
64+
Calculates the squarefree numbers inside unique_coefficients.
13465
13566
Based on the definition of a non-squarefree number, then any non-squarefree
13667
n can be decomposed as n = p*p*r, where p is positive prime number and r
@@ -140,27 +71,27 @@ def get_squarefree(
14071
squarefree as r cannot be negative. On the contrary, if any r exists such
14172
that n = p*p*r, then the number is non-squarefree.
14273
143-
>>> get_squarefree({1}, [])
144-
set()
145-
>>> get_squarefree({1, 2}, [])
146-
set()
147-
>>> get_squarefree({1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21}, [4, 9, 25])
74+
>>> get_squarefrees({1})
75+
{1}
76+
>>> get_squarefrees({1, 2})
77+
{1, 2}
78+
>>> get_squarefrees({1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21})
14879
{1, 2, 3, 5, 6, 7, 35, 10, 15, 21}
14980
"""
15081

151-
if len(squared_primes) == 0:
152-
return set()
153-
15482
non_squarefrees = set()
155-
prime_squared_idx = 0
156-
for num in sorted(unique_coefficients):
157-
prime_squared_idx = get_squared_primes_to_use(
158-
num, squared_primes, prime_squared_idx
159-
)
160-
if prime_squared_idx == -1:
161-
continue
162-
if any(num % prime == 0 for prime in squared_primes[:prime_squared_idx]):
163-
non_squarefrees.add(num)
83+
for number in unique_coefficients:
84+
divisor = 2
85+
copy_number = number
86+
while divisor**2 <= copy_number:
87+
multiplicity = 0
88+
while copy_number % divisor == 0:
89+
copy_number //= divisor
90+
multiplicity += 1
91+
if multiplicity >= 2:
92+
non_squarefrees.add(number)
93+
break
94+
divisor += 1
16495

16596
return unique_coefficients.difference(non_squarefrees)
16697

@@ -170,15 +101,14 @@ def solution(n: int = 51) -> int:
170101
Returns the sum of squarefrees for a given Pascal's Triangle of depth n.
171102
172103
>>> solution(1)
173-
0
104+
1
174105
>>> solution(8)
175106
105
176107
>>> solution(9)
177108
175
178109
"""
179110
unique_coefficients = get_pascal_triangle_unique_coefficients(n)
180-
primes = get_primes_squared(max(unique_coefficients))
181-
squarefrees = get_squarefree(unique_coefficients, primes)
111+
squarefrees = get_squarefrees(unique_coefficients)
182112
return sum(squarefrees)
183113

184114

0 commit comments

Comments
 (0)