Skip to content

Commit 15e20e8

Browse files
committed
Added solution for Project Euler problem 74. Fixes: #2695
1 parent c9500dc commit 15e20e8

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed

project_euler/problem_74/__init__.py

Whitespace-only changes.

project_euler/problem_74/sol1.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
The number 145 is well known for the property that the sum of the factorial of its
3+
digits is equal to 145:
4+
5+
1! + 4! + 5! = 1 + 24 + 120 = 145
6+
7+
Perhaps less well known is 169, in that it produces the longest chain of numbers that
8+
link back to 169; it turns out that there are only three such loops that exist:
9+
10+
169 → 363601 → 1454 → 169
11+
871 → 45361 → 871
12+
872 → 45362 → 872
13+
14+
It is not difficult to prove that EVERY starting number will eventually get stuck in
15+
a loop. For example,
16+
17+
69 → 363600 → 1454 → 169 → 363601 (→ 1454)
18+
78 → 45360 → 871 → 45361 (→ 871)
19+
540 → 145 (→ 145)
20+
21+
Starting with 69 produces a chain of five non-repeating terms, but the longest
22+
non-repeating chain with a starting number below one million is sixty terms.
23+
24+
How many chains, with a starting number below one million, contain exactly sixty
25+
non-repeating terms?
26+
"""
27+
28+
29+
DIGIT_FACTORIALS = {
30+
"0": 1,
31+
"1": 1,
32+
"2": 2,
33+
"3": 6,
34+
"4": 24,
35+
"5": 120,
36+
"6": 720,
37+
"7": 5040,
38+
"8": 40320,
39+
"9": 362880,
40+
}
41+
42+
CACHE_SUM_DIGIT_FACTORIALS = {145: 145}
43+
44+
CHAIN_LENGTH_CACHE = {
45+
145: 0,
46+
169: 3,
47+
36301: 3,
48+
1454: 3,
49+
871: 2,
50+
45361: 2,
51+
872: 2,
52+
45361: 2,
53+
}
54+
55+
56+
def sum_digit_factorials(n: int) -> int:
57+
"""
58+
Return the sum of the factorial of the digits of n.
59+
>>> sum_digit_factorials(145)
60+
145
61+
>>> sum_digit_factorials(45361)
62+
871
63+
>>> sum_digit_factorials(540)
64+
145
65+
"""
66+
if n in CACHE_SUM_DIGIT_FACTORIALS:
67+
return CACHE_SUM_DIGIT_FACTORIALS[n]
68+
ret = sum([DIGIT_FACTORIALS[let] for let in str(n)])
69+
CACHE_SUM_DIGIT_FACTORIALS[n] = ret
70+
return ret
71+
72+
73+
def chain_length(n: int, previous: set = None) -> int:
74+
"""
75+
Calculate the length of the chain of non-repeating terms starting with n.
76+
Previous is a set containing the previous member of the chain.
77+
>>> chain_length(10101)
78+
11
79+
>>> chain_length(555)
80+
20
81+
>>> chain_length(178924)
82+
39
83+
"""
84+
previous = previous or set()
85+
if n in CHAIN_LENGTH_CACHE:
86+
return CHAIN_LENGTH_CACHE[n]
87+
next_number = sum_digit_factorials(n)
88+
if next_number in previous:
89+
CHAIN_LENGTH_CACHE[n] = 0
90+
return 0
91+
else:
92+
previous.add(n)
93+
ret = 1 + chain_length(next_number, previous)
94+
CHAIN_LENGTH_CACHE[n] = ret
95+
return ret
96+
97+
98+
def solution(n: int = 60) -> int:
99+
"""
100+
Return the number of chains with a starting number below one million which
101+
contain exactly n non-repeating terms.
102+
"""
103+
return sum(1 for i in range(1, 1000000) if chain_length(i) == n)
104+
105+
106+
if __name__ == "__main__":
107+
print(solution())

0 commit comments

Comments
 (0)