Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ff00bfa

Browse files
authoredNov 3, 2020
Added a solution for Project Euler Problem 203 "Squarefree Binomial Coefficients" (TheAlgorithms#3513)
* Added a solution for Project Euler Problem 203 (https://projecteuler.net/problem=203) * Simplified loop that calculates the coefficients of the Pascal's Triangle. Changes based on review suggestion. * Moved get_squared_primes_to_use function outside the get_squarefree function and fixed a failing doctest with the former.
1 parent eaa7ef4 commit ff00bfa

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed
 

‎project_euler/problem_203/__init__.py

Whitespace-only changes.

‎project_euler/problem_203/sol1.py

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
"""
2+
Project Euler Problem 203: https://projecteuler.net/problem=203
3+
4+
The binomial coefficients (n k) can be arranged in triangular form, Pascal's
5+
triangle, like this:
6+
1
7+
1 1
8+
1 2 1
9+
1 3 3 1
10+
1 4 6 4 1
11+
1 5 10 10 5 1
12+
1 6 15 20 15 6 1
13+
1 7 21 35 35 21 7 1
14+
.........
15+
16+
It can be seen that the first eight rows of Pascal's triangle contain twelve
17+
distinct numbers: 1, 2, 3, 4, 5, 6, 7, 10, 15, 20, 21 and 35.
18+
19+
A positive integer n is called squarefree if no square of a prime divides n.
20+
Of the twelve distinct numbers in the first eight rows of Pascal's triangle,
21+
all except 4 and 20 are squarefree. The sum of the distinct squarefree numbers
22+
in the first eight rows is 105.
23+
24+
Find the sum of the distinct squarefree numbers in the first 51 rows of
25+
Pascal's triangle.
26+
27+
References:
28+
- https://en.wikipedia.org/wiki/Pascal%27s_triangle
29+
"""
30+
31+
import math
32+
from typing import List, Set
33+
34+
35+
def get_pascal_triangle_unique_coefficients(depth: int) -> Set[int]:
36+
"""
37+
Returns the unique coefficients of a Pascal's triangle of depth "depth".
38+
39+
The coefficients of this triangle are symmetric. A further improvement to this
40+
method could be to calculate the coefficients once per level. Nonetheless,
41+
the current implementation is fast enough for the original problem.
42+
43+
>>> get_pascal_triangle_unique_coefficients(1)
44+
{1}
45+
>>> get_pascal_triangle_unique_coefficients(2)
46+
{1}
47+
>>> get_pascal_triangle_unique_coefficients(3)
48+
{1, 2}
49+
>>> get_pascal_triangle_unique_coefficients(8)
50+
{1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21}
51+
"""
52+
coefficients = {1}
53+
previous_coefficients = [1]
54+
for step in range(2, depth + 1):
55+
coefficients_begins_one = previous_coefficients + [0]
56+
coefficients_ends_one = [0] + previous_coefficients
57+
previous_coefficients = []
58+
for x, y in zip(coefficients_begins_one, coefficients_ends_one):
59+
coefficients.add(x + y)
60+
previous_coefficients.append(x + y)
61+
return coefficients
62+
63+
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 = round(math.sqrt(max_number))
79+
non_primes = set()
80+
primes = []
81+
for num in range(2, max_prime + 1):
82+
if num in non_primes:
83+
continue
84+
85+
counter = 2
86+
while num * counter <= max_prime:
87+
non_primes.add(num * counter)
88+
counter += 1
89+
90+
primes.append(num ** 2)
91+
return primes
92+
93+
94+
def get_squared_primes_to_use(
95+
num_to_look: int, squared_primes: List[int], previous_index: int
96+
) -> int:
97+
"""
98+
Returns an int indicating the last index on which squares of primes
99+
in primes are lower than num_to_look.
100+
101+
This method supposes that squared_primes is sorted in ascending order and that
102+
each num_to_look is provided in ascending order as well. Under these
103+
assumptions, it needs a previous_index parameter that tells what was
104+
the index returned by the method for the previous num_to_look.
105+
106+
If all the elements in squared_primes are greater than num_to_look, then the
107+
method returns -1.
108+
109+
>>> get_squared_primes_to_use(1, [4, 9, 16, 25], 0)
110+
-1
111+
>>> get_squared_primes_to_use(4, [4, 9, 16, 25], 0)
112+
1
113+
>>> get_squared_primes_to_use(16, [4, 9, 16, 25], 1)
114+
3
115+
"""
116+
idx = max(previous_index, 0)
117+
118+
while idx < len(squared_primes) and squared_primes[idx] <= num_to_look:
119+
idx += 1
120+
121+
if idx == 0 and squared_primes[idx] > num_to_look:
122+
return -1
123+
124+
if idx == len(squared_primes) and squared_primes[-1] > num_to_look:
125+
return -1
126+
127+
return idx
128+
129+
130+
def get_squarefree(
131+
unique_coefficients: Set[int], squared_primes: List[int]
132+
) -> Set[int]:
133+
"""
134+
Calculates the squarefree numbers inside unique_coefficients given a
135+
list of square of primes.
136+
137+
Based on the definition of a non-squarefree number, then any non-squarefree
138+
n can be decomposed as n = p*p*r, where p is positive prime number and r
139+
is a positive integer.
140+
141+
Under the previous formula, any coefficient that is lower than p*p is
142+
squarefree as r cannot be negative. On the contrary, if any r exists such
143+
that n = p*p*r, then the number is non-squarefree.
144+
145+
>>> get_squarefree({1}, [])
146+
set()
147+
>>> get_squarefree({1, 2}, [])
148+
set()
149+
>>> get_squarefree({1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21}, [4, 9, 25])
150+
{1, 2, 3, 5, 6, 7, 35, 10, 15, 21}
151+
"""
152+
153+
if len(squared_primes) == 0:
154+
return set()
155+
156+
non_squarefrees = set()
157+
prime_squared_idx = 0
158+
for num in sorted(unique_coefficients):
159+
prime_squared_idx = get_squared_primes_to_use(
160+
num, squared_primes, prime_squared_idx
161+
)
162+
if prime_squared_idx == -1:
163+
continue
164+
if any(num % prime == 0 for prime in squared_primes[:prime_squared_idx]):
165+
non_squarefrees.add(num)
166+
167+
return unique_coefficients.difference(non_squarefrees)
168+
169+
170+
def solution(n: int = 51) -> int:
171+
"""
172+
Returns the sum of squarefrees for a given Pascal's Triangle of depth n.
173+
174+
>>> solution(1)
175+
0
176+
>>> solution(8)
177+
105
178+
>>> solution(9)
179+
175
180+
"""
181+
unique_coefficients = get_pascal_triangle_unique_coefficients(n)
182+
primes = get_primes_squared(max(unique_coefficients))
183+
squarefrees = get_squarefree(unique_coefficients, primes)
184+
return sum(squarefrees)
185+
186+
187+
if __name__ == "__main__":
188+
print(f"{solution() = }")

0 commit comments

Comments
 (0)
Failed to load comments.