Skip to content

Commit d567a9e

Browse files
b63cclauss
authored andcommitted
solution to problem 551 from project euler (TheAlgorithms#1164)
* solution to problem 551 from project euler * renamed variables, and added more comments to improve readabilty
1 parent d4151bd commit d567a9e

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

project_euler/problem_551/__init__.py

Whitespace-only changes.

project_euler/problem_551/sol1.py

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
"""
2+
Sum of digits sequence
3+
Problem 551
4+
5+
Let a(0), a(1),... be an interger sequence defined by:
6+
a(0) = 1
7+
for n >= 1, a(n) is the sum of the digits of all preceding terms
8+
9+
The sequence starts with 1, 1, 2, 4, 8, ...
10+
You are given a(10^6) = 31054319.
11+
12+
Find a(10^15)
13+
"""
14+
15+
ks = [k for k in range(2, 20+1)]
16+
base = [10 ** k for k in range(ks[-1] + 1)]
17+
memo = {}
18+
19+
20+
def next_term(a_i, k, i, n):
21+
"""
22+
Calculates and updates a_i in-place to either the n-th term or the
23+
smallest term for which c > 10^k when the terms are written in the form:
24+
a(i) = b * 10^k + c
25+
26+
For any a(i), if digitsum(b) and c have the same value, the difference
27+
between subsequent terms will be the same until c >= 10^k. This difference
28+
is cached to greatly speed up the computation.
29+
30+
Arguments:
31+
a_i -- array of digits starting from the one's place that represent
32+
the i-th term in the sequence
33+
k -- k when terms are written in the from a(i) = b*10^k + c.
34+
Term are calulcated until c > 10^k or the n-th term is reached.
35+
i -- position along the sequence
36+
n -- term to caluclate up to if k is large enough
37+
38+
Return: a tuple of difference between ending term and starting term, and
39+
the number of terms calculated. ex. if starting term is a_0=1, and
40+
ending term is a_10=62, then (61, 9) is returned.
41+
"""
42+
# ds_b - digitsum(b)
43+
ds_b = 0
44+
for j in range(k, len(a_i)):
45+
ds_b += a_i[j]
46+
c = 0
47+
for j in range(min(len(a_i), k)):
48+
c += a_i[j] * base[j]
49+
50+
diff, dn = 0, 0
51+
max_dn = n - i
52+
53+
sub_memo = memo.get(ds_b)
54+
55+
if sub_memo != None:
56+
jumps = sub_memo.get(c)
57+
58+
if jumps != None and len(jumps) > 0:
59+
# find and make the largest jump without going over
60+
max_jump = -1
61+
for _k in range(len(jumps) - 1, -1, -1):
62+
if jumps[_k][2] <= k and jumps[_k][1] <= max_dn:
63+
max_jump = _k
64+
break
65+
66+
if max_jump >= 0:
67+
diff, dn, _kk = jumps[max_jump]
68+
# since the difference between jumps is cached, add c
69+
new_c = diff + c
70+
for j in range(min(k, len(a_i))):
71+
new_c, a_i[j] = divmod(new_c, 10)
72+
if new_c > 0:
73+
add(a_i, k, new_c)
74+
75+
else:
76+
sub_memo[c] = []
77+
else:
78+
sub_memo = {c: []}
79+
memo[ds_b] = sub_memo
80+
81+
if dn >= max_dn or c + diff >= base[k]:
82+
return diff, dn
83+
84+
if k > ks[0]:
85+
while True:
86+
# keep doing smaller jumps
87+
_diff, terms_jumped = next_term(a_i, k - 1, i + dn, n)
88+
diff += _diff
89+
dn += terms_jumped
90+
91+
if dn >= max_dn or c + diff >= base[k]:
92+
break
93+
else:
94+
# would be too small a jump, just compute sequential terms instead
95+
_diff, terms_jumped = compute(a_i, k, i + dn, n)
96+
diff += _diff
97+
dn += terms_jumped
98+
99+
jumps = sub_memo[c]
100+
101+
# keep jumps sorted by # of terms skipped
102+
j = 0
103+
while j < len(jumps):
104+
if jumps[j][1] > dn:
105+
break
106+
j += 1
107+
108+
# cache the jump for this value digitsum(b) and c
109+
sub_memo[c].insert(j, (diff, dn, k))
110+
return (diff, dn)
111+
112+
113+
def compute(a_i, k, i, n):
114+
"""
115+
same as next_term(a_i, k, i, n) but computes terms without memoizing results.
116+
"""
117+
if i >= n:
118+
return 0, i
119+
if k > len(a_i):
120+
a_i.extend([0 for _ in range(k - len(a_i))])
121+
122+
# note: a_i -> b * 10^k + c
123+
# ds_b -> digitsum(b)
124+
# ds_c -> digitsum(c)
125+
start_i = i
126+
ds_b, ds_c, diff = 0, 0, 0
127+
for j in range(len(a_i)):
128+
if j >= k:
129+
ds_b += a_i[j]
130+
else:
131+
ds_c += a_i[j]
132+
133+
while i < n:
134+
i += 1
135+
addend = ds_c + ds_b
136+
diff += addend
137+
ds_c = 0
138+
for j in range(k):
139+
s = a_i[j] + addend
140+
addend, a_i[j] = divmod(s, 10)
141+
142+
ds_c += a_i[j]
143+
144+
if addend > 0:
145+
break
146+
147+
if addend > 0:
148+
add(a_i, k, addend)
149+
return diff, i - start_i
150+
151+
152+
def add(digits, k, addend):
153+
"""
154+
adds addend to digit array given in digits
155+
starting at index k
156+
"""
157+
for j in range(k, len(digits)):
158+
s = digits[j] + addend
159+
if s >= 10:
160+
quotient, digits[j] = divmod(s, 10)
161+
addend = addend // 10 + quotient
162+
else:
163+
digits[j] = s
164+
addend = addend // 10
165+
166+
if addend == 0:
167+
break
168+
169+
while addend > 0:
170+
addend, digit = divmod(addend, 10)
171+
digits.append(digit)
172+
173+
174+
def solution(n):
175+
"""
176+
returns n-th term of sequence
177+
178+
>>> solution(10)
179+
62
180+
181+
>>> solution(10**6)
182+
31054319
183+
184+
>>> solution(10**15)
185+
73597483551591773
186+
"""
187+
188+
digits = [1]
189+
i = 1
190+
dn = 0
191+
while True:
192+
diff, terms_jumped = next_term(digits, 20, i + dn, n)
193+
dn += terms_jumped
194+
if dn == n - i:
195+
break
196+
197+
a_n = 0
198+
for j in range(len(digits)):
199+
a_n += digits[j] * 10 ** j
200+
return a_n
201+
202+
203+
if __name__ == "__main__":
204+
print(solution(10 ** 15))

0 commit comments

Comments
 (0)