Skip to content

Commit d5c56d2

Browse files
fpringlegithub-actions
authored andcommitted
Add solution for Project Euler problem 101 (TheAlgorithms#4033)
* Added solution for Project Euler problem 101 * Got rid of map functions * updating DIRECTORY.md * Better function/variable names * Better variable names * Type hints * Doctest for nested function Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
1 parent 1663b4f commit d5c56d2

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

Diff for: DIRECTORY.md

+2
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,8 @@
744744
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py)
745745
* Problem 099
746746
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py)
747+
* Problem 101
748+
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_101/sol1.py)
747749
* Problem 112
748750
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py)
749751
* Problem 113

Diff for: project_euler/problem_101/__init__.py

Whitespace-only changes.

Diff for: project_euler/problem_101/sol1.py

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""
2+
If we are presented with the first k terms of a sequence it is impossible to say with
3+
certainty the value of the next term, as there are infinitely many polynomial functions
4+
that can model the sequence.
5+
6+
As an example, let us consider the sequence of cube
7+
numbers. This is defined by the generating function,
8+
u(n) = n3: 1, 8, 27, 64, 125, 216, ...
9+
10+
Suppose we were only given the first two terms of this sequence. Working on the
11+
principle that "simple is best" we should assume a linear relationship and predict the
12+
next term to be 15 (common difference 7). Even if we were presented with the first three
13+
terms, by the same principle of simplicity, a quadratic relationship should be
14+
assumed.
15+
16+
We shall define OP(k, n) to be the nth term of the optimum polynomial
17+
generating function for the first k terms of a sequence. It should be clear that
18+
OP(k, n) will accurately generate the terms of the sequence for n ≤ k, and potentially
19+
the first incorrect term (FIT) will be OP(k, k+1); in which case we shall call it a
20+
bad OP (BOP).
21+
22+
As a basis, if we were only given the first term of sequence, it would be most
23+
sensible to assume constancy; that is, for n ≥ 2, OP(1, n) = u(1).
24+
25+
Hence we obtain the
26+
following OPs for the cubic sequence:
27+
28+
OP(1, n) = 1 1, 1, 1, 1, ...
29+
OP(2, n) = 7n-6 1, 8, 15, ...
30+
OP(3, n) = 6n^2-11n+6 1, 8, 27, 58, ...
31+
OP(4, n) = n^3 1, 8, 27, 64, 125, ...
32+
33+
Clearly no BOPs exist for k ≥ 4.
34+
35+
By considering the sum of FITs generated by the BOPs (indicated in red above), we
36+
obtain 1 + 15 + 58 = 74.
37+
38+
Consider the following tenth degree polynomial generating function:
39+
40+
1 - n + n^2 - n^3 + n^4 - n^5 + n^6 - n^7 + n^8 - n^9 + n^10
41+
42+
Find the sum of FITs for the BOPs.
43+
"""
44+
45+
46+
from typing import Callable, List, Union
47+
48+
Matrix = List[List[Union[float, int]]]
49+
50+
51+
def solve(matrix: Matrix, vector: Matrix) -> Matrix:
52+
"""
53+
Solve the linear system of equations Ax = b (A = "matrix", b = "vector")
54+
for x using Gaussian elimination and back substitution. We assume that A
55+
is an invertible square matrix and that b is a column vector of the
56+
same height.
57+
>>> solve([[1, 0], [0, 1]], [[1],[2]])
58+
[[1.0], [2.0]]
59+
>>> solve([[2, 1, -1],[-3, -1, 2],[-2, 1, 2]],[[8], [-11],[-3]])
60+
[[2.0], [3.0], [-1.0]]
61+
"""
62+
size: int = len(matrix)
63+
augmented: Matrix = [[0 for _ in range(size + 1)] for _ in range(size)]
64+
row: int
65+
row2: int
66+
col: int
67+
col2: int
68+
pivot_row: int
69+
ratio: float
70+
71+
for row in range(size):
72+
for col in range(size):
73+
augmented[row][col] = matrix[row][col]
74+
75+
augmented[row][size] = vector[row][0]
76+
77+
row = 0
78+
col = 0
79+
while row < size and col < size:
80+
# pivoting
81+
pivot_row = max(
82+
[(abs(augmented[row2][col]), row2) for row2 in range(col, size)]
83+
)[1]
84+
if augmented[pivot_row][col] == 0:
85+
col += 1
86+
continue
87+
else:
88+
augmented[row], augmented[pivot_row] = augmented[pivot_row], augmented[row]
89+
90+
for row2 in range(row + 1, size):
91+
ratio = augmented[row2][col] / augmented[row][col]
92+
augmented[row2][col] = 0
93+
for col2 in range(col + 1, size + 1):
94+
augmented[row2][col2] -= augmented[row][col2] * ratio
95+
96+
row += 1
97+
col += 1
98+
99+
# back substitution
100+
for col in range(1, size):
101+
for row in range(col):
102+
ratio = augmented[row][col] / augmented[col][col]
103+
for col2 in range(col, size + 1):
104+
augmented[row][col2] -= augmented[col][col2] * ratio
105+
106+
# round to get rid of numbers like 2.000000000000004
107+
return [
108+
[round(augmented[row][size] / augmented[row][row], 10)] for row in range(size)
109+
]
110+
111+
112+
def interpolate(y_list: List[int]) -> Callable[[int], int]:
113+
"""
114+
Given a list of data points (1,y0),(2,y1), ..., return a function that
115+
interpolates the data points. We find the coefficients of the interpolating
116+
polynomial by solving a system of linear equations corresponding to
117+
x = 1, 2, 3...
118+
119+
>>> interpolate([1])(3)
120+
1
121+
>>> interpolate([1, 8])(3)
122+
15
123+
>>> interpolate([1, 8, 27])(4)
124+
58
125+
>>> interpolate([1, 8, 27, 64])(6)
126+
216
127+
"""
128+
129+
size: int = len(y_list)
130+
matrix: Matrix = [[0 for _ in range(size)] for _ in range(size)]
131+
vector: Matrix = [[0] for _ in range(size)]
132+
coeffs: Matrix
133+
x_val: int
134+
y_val: int
135+
col: int
136+
137+
for x_val, y_val in enumerate(y_list):
138+
for col in range(size):
139+
matrix[x_val][col] = (x_val + 1) ** (size - col - 1)
140+
vector[x_val][0] = y_val
141+
142+
coeffs = solve(matrix, vector)
143+
144+
def interpolated_func(var: int) -> int:
145+
"""
146+
>>> interpolate([1])(3)
147+
1
148+
>>> interpolate([1, 8])(3)
149+
15
150+
>>> interpolate([1, 8, 27])(4)
151+
58
152+
>>> interpolate([1, 8, 27, 64])(6)
153+
216
154+
"""
155+
return sum(
156+
round(coeffs[x_val][0]) * (var ** (size - x_val - 1))
157+
for x_val in range(size)
158+
)
159+
160+
return interpolated_func
161+
162+
163+
def question_function(variable: int) -> int:
164+
"""
165+
The generating function u as specified in the question.
166+
>>> question_function(0)
167+
1
168+
>>> question_function(1)
169+
1
170+
>>> question_function(5)
171+
8138021
172+
>>> question_function(10)
173+
9090909091
174+
"""
175+
return (
176+
1
177+
- variable
178+
+ variable ** 2
179+
- variable ** 3
180+
+ variable ** 4
181+
- variable ** 5
182+
+ variable ** 6
183+
- variable ** 7
184+
+ variable ** 8
185+
- variable ** 9
186+
+ variable ** 10
187+
)
188+
189+
190+
def solution(func: Callable[[int], int] = question_function, order: int = 10) -> int:
191+
"""
192+
Find the sum of the FITs of the BOPS. For each interpolating polynomial of order
193+
1, 2, ... , 10, find the first x such that the value of the polynomial at x does
194+
not equal u(x).
195+
>>> solution(lambda n: n ** 3, 3)
196+
74
197+
"""
198+
data_points: List[int] = [func(x_val) for x_val in range(1, order + 1)]
199+
200+
polynomials: List[Callable[[int], int]] = [
201+
interpolate(data_points[:max_coeff]) for max_coeff in range(1, order + 1)
202+
]
203+
204+
ret: int = 0
205+
poly: int
206+
x_val: int
207+
208+
for poly in polynomials:
209+
x_val = 1
210+
while func(x_val) == poly(x_val):
211+
x_val += 1
212+
213+
ret += poly(x_val)
214+
215+
return ret
216+
217+
218+
if __name__ == "__main__":
219+
print(f"{solution() = }")

0 commit comments

Comments
 (0)