Skip to content

Commit d1e4849

Browse files
committed
Problem 61 project euler completed
1 parent 2a4d2b7 commit d1e4849

1 file changed

Lines changed: 346 additions & 0 deletions

File tree

project_euler/problem_061/sol1.py

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
"""
2+
Project Euler Problem 061: https://projecteuler.net/problem=61
3+
4+
Problem statement -
5+
Triangle, square, pentagonal, hexagonal, heptagonal, and octagonal numbers are all
6+
figurate (polygonal) numbers and are generated by the following formulae:
7+
8+
Triangle P(3,n) = n(n+1)/2 1,3,6,10,15,...
9+
Square P(4,n) = n^2 1,4,9,16,25,...
10+
Pentagonal P(5,n) = n(3n-1)/2 1,5,12,22,35,...
11+
Hexagonal P(6,n) = n(2n-1) 1,6,15,28,45,...
12+
Heptagonal P(7,n) = n(5n-3)/2 1,7,18,34,55,...
13+
Octagonal P(8,n) = n(3n-2) 1,8,21,40,65,...
14+
15+
The ordered set of three 4-digit numbers: 8128, 2882, 8281,
16+
has three interesting properties.
17+
18+
1. The set is cyclic, in that the last two digits of each number is the first two
19+
digits of the next number (including the last number with the first).
20+
2. Each polygonal type: triangle(P(3,127)), square(P(4,91)), and pentagonal(P(5,44)),
21+
is represented by a different number in the set.
22+
3. This is the only set of 4-digit numbers with this property.
23+
24+
Find the sum of the only ordered set of six cyclic -digit numbers for which each
25+
polygonal type: triangle, square, pentagonal, hexagonal, heptagonal, and octagonal,
26+
is represented by a different number in the set.
27+
28+
29+
Solution explanation-
30+
The solution is actually pretty simple using a brute force approach.
31+
We first do some precomputations to get all the relevant sets of polygon numbers.
32+
Individual helper functions have been made for the same.
33+
We also have one more helper function to check if two given numbers are cyclic or not.
34+
35+
Now the approach is to pick one number from a set then try to find a number in another
36+
set that is cyclic to the previous. Now consider this new number as previous and find
37+
a new number in another set(set that has not been used yet).Continue doing this until
38+
you either reach your last set, in which case you need only check for cyclic against
39+
the first number, if it matches you have an answer. If you cannot find a cyclic number
40+
for previous in your current set, you backtrack to the prev set and chose the next
41+
number from it.
42+
43+
So code wise, we generate all possiblee permutations of the sets using itertools built
44+
in permutations method. Then its justa bunch of for loops trying to find the answer.
45+
Once we have a valid ordered set, we return its sum.
46+
47+
"""
48+
49+
import itertools
50+
51+
52+
def fill_triangle(int_range: tuple[int, int] = (1000, 9999)) -> list[int]:
53+
"""
54+
Populates the triangle array with relevant integers i.e greater than
55+
range[0] and less than range[1]+1.
56+
The range provided should be inclusive.
57+
58+
>>> fill_triangle((1,20))
59+
[1, 3, 6, 10, 15]
60+
61+
"""
62+
63+
def triangle(n: int) -> int:
64+
return n * (n + 1) // 2
65+
66+
st, end = int_range
67+
arr = []
68+
i = 1
69+
while True:
70+
k = triangle(i)
71+
if k < st:
72+
i += 1
73+
continue
74+
if k > end:
75+
break
76+
arr.append(k)
77+
i += 1
78+
return arr
79+
80+
81+
def fill_square(int_range: tuple[int, int] = (1000, 9999)) -> list[int]:
82+
"""
83+
Populates the square array with relevant integers i.e greater than
84+
range[0] and less than range[1]+1.
85+
The range provided should be inclusive.
86+
87+
>>> fill_square((1,30))
88+
[1, 4, 9, 16, 25]
89+
90+
"""
91+
92+
def square(n: int) -> int:
93+
return n**2
94+
95+
st, end = int_range
96+
arr = []
97+
i = 1
98+
while True:
99+
k = square(i)
100+
if k < st:
101+
i += 1
102+
continue
103+
if k > end:
104+
break
105+
arr.append(k)
106+
i += 1
107+
return arr
108+
109+
110+
def fill_pentagonal(int_range: tuple[int, int] = (1000, 9999)) -> list[int]:
111+
"""
112+
Populates the pentagonal array with relevant integers i.e greater than
113+
range[0] and less than range[1]+1.
114+
The range provided should be inclusive.
115+
116+
>>> fill_pentagonal((1,40))
117+
[1, 5, 12, 22, 35]
118+
119+
"""
120+
121+
def pentagon(n: int) -> int:
122+
return n * (3 * n - 1) // 2
123+
124+
st, end = int_range
125+
arr = []
126+
i = 1
127+
while True:
128+
k = pentagon(i)
129+
if k < st:
130+
i += 1
131+
continue
132+
if k > end:
133+
break
134+
arr.append(k)
135+
i += 1
136+
return arr
137+
138+
139+
def fill_hexagonal(int_range: tuple[int, int] = (1000, 9999)) -> list[int]:
140+
"""
141+
Populates the hexagonal array with relevant integers i.e greater than
142+
range[0] and less than range[1]+1.
143+
The range provided should be inclusive.
144+
145+
>>> fill_hexagonal((1,50))
146+
[1, 6, 15, 28, 45]
147+
148+
"""
149+
150+
def hexagon(n: int) -> int:
151+
return n * (2 * n - 1)
152+
153+
st, end = int_range
154+
arr = []
155+
i = 1
156+
while True:
157+
k = hexagon(i)
158+
if k < st:
159+
i += 1
160+
continue
161+
if k > end:
162+
break
163+
arr.append(k)
164+
i += 1
165+
return arr
166+
167+
168+
def fill_heptagonal(int_range: tuple[int, int] = (1000, 9999)) -> list[int]:
169+
"""
170+
Populates the heptagonal array with relevant integers i.e greater than
171+
range[0] and less than range[1]+1.
172+
The range provided should be inclusive.
173+
174+
>>> fill_heptagonal((1,60))
175+
[1, 7, 18, 34, 55]
176+
177+
"""
178+
179+
def heptagon(n: int) -> int:
180+
return n * (5 * n - 3) // 2
181+
182+
st, end = int_range
183+
arr = []
184+
i = 1
185+
while True:
186+
k = heptagon(i)
187+
if k < st:
188+
i += 1
189+
continue
190+
if k > end:
191+
break
192+
arr.append(k)
193+
i += 1
194+
return arr
195+
196+
197+
def fill_octagonal(int_range: tuple[int, int] = (1000, 9999)) -> list[int]:
198+
"""
199+
Populates the octagonal array with relevant integers i.e greater than
200+
range[0] and less than range[1]+1.
201+
The range provided should be inclusive.
202+
203+
>>> fill_octagonal((1,70))
204+
[1, 8, 21, 40, 65]
205+
206+
207+
"""
208+
209+
def octagon(n: int) -> int:
210+
return n * (3 * n - 2)
211+
212+
st, end = int_range
213+
arr = []
214+
i = 1
215+
while True:
216+
k = octagon(i)
217+
if k < st:
218+
i += 1
219+
continue
220+
if k > end:
221+
break
222+
arr.append(k)
223+
i += 1
224+
return arr
225+
226+
227+
def check_cyclic(x: int, y: int) -> bool:
228+
"""
229+
This function checks if two 4 digit numbers are cyclic.
230+
For this problem we are only concerned with 4 digit numbers.
231+
232+
Raises:
233+
ValueError
234+
235+
236+
>>> check_cyclic(8228, 2810)
237+
True
238+
>>> check_cyclic(8228, 2410)
239+
False
240+
241+
"""
242+
243+
if x < 1000 or y < 1000:
244+
raise ValueError("Both integers must be greater than 999")
245+
if x > 9999 or y > 9999:
246+
raise ValueError("Both integers must be less than 10000")
247+
248+
return (x % 100) == (y // 100)
249+
250+
251+
def solution() -> int:
252+
"""
253+
The function gives a solution to problem 061 of project Euler.
254+
The function does some precomputations first to get all the
255+
relevant sets of polygon numbers then applies a for loop on
256+
all possible permutations of the sets and check for a valid
257+
answer.
258+
259+
>>> solution()
260+
28684
261+
262+
"""
263+
264+
# Make initial arrays to store all types of numbers in individual arrays.
265+
# This will make accessing them easier in the future.
266+
# We will be fill each of these arrays only considering numbers greater
267+
# than 999 and less than 10000.
268+
# Since we will add them in a systematic manner, these will be present
269+
# in a sorted order(increasing order).
270+
triangle = fill_triangle()
271+
square = fill_square()
272+
pentagonal = fill_pentagonal()
273+
hexagonal = fill_hexagonal()
274+
heptagonal = fill_heptagonal()
275+
octagonal = fill_octagonal()
276+
277+
# create a dictionary of all polygons for access later
278+
polygon_dict = {
279+
0: triangle,
280+
1: square,
281+
2: pentagonal,
282+
3: hexagonal,
283+
4: heptagonal,
284+
5: octagonal,
285+
}
286+
287+
answer = []
288+
289+
# The elements can be in any order from any of the sets,
290+
# so we need to consider all possible permutations of the sets.
291+
permutations = list(itertools.permutations(range(6)))
292+
293+
# Now we simply apply a for loop to consider all permutations and
294+
# check if that permutation leads to a valid answer.
295+
for perm in permutations:
296+
first_set = polygon_dict[perm[0]]
297+
second_set = polygon_dict[perm[1]]
298+
third_set = polygon_dict[perm[2]]
299+
fourth_set = polygon_dict[perm[3]]
300+
fifth_set = polygon_dict[perm[4]]
301+
sixth_set = polygon_dict[perm[5]]
302+
for first in first_set:
303+
for second in second_set:
304+
prev = first
305+
if not (check_cyclic(prev, second)):
306+
continue
307+
for third in third_set:
308+
prev = second
309+
if not (check_cyclic(prev, third)):
310+
continue
311+
for fourth in fourth_set:
312+
prev = third
313+
if not (check_cyclic(prev, fourth)):
314+
continue
315+
for fifth in fifth_set:
316+
prev = fourth
317+
if not (check_cyclic(prev, fifth)):
318+
continue
319+
for sixth in sixth_set:
320+
prev = fifth
321+
if not (check_cyclic(prev, sixth)):
322+
continue
323+
if check_cyclic(sixth, first):
324+
answer = [
325+
first,
326+
second,
327+
third,
328+
fourth,
329+
fifth,
330+
sixth,
331+
]
332+
# print(answer)
333+
return sum(answer)
334+
335+
336+
if __name__ == "__main__":
337+
# import time
338+
339+
# start_time = time.perf_counter()
340+
341+
print(f"{solution() = }")
342+
343+
# end_time = time.perf_counter()
344+
345+
# execution_time = end_time - start_time
346+
# print(f"Execution time: {execution_time:.6f} seconds")

0 commit comments

Comments
 (0)