Skip to content

Commit b720f24

Browse files
mindauglMaximSmolskiypre-commit-ci[bot]
authored
Add solution for the Euler project problem 95. (#12669)
* Add documentation and tests for the Euler project problem 95 solution. * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py * Update sol1.py --------- Co-authored-by: Maxim Smolskiy <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent d9d56b1 commit b720f24

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

project_euler/problem_095/__init__.py

Whitespace-only changes.

project_euler/problem_095/sol1.py

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""
2+
Project Euler Problem 95: https://projecteuler.net/problem=95
3+
4+
Amicable Chains
5+
6+
The proper divisors of a number are all the divisors excluding the number itself.
7+
For example, the proper divisors of 28 are 1, 2, 4, 7, and 14.
8+
As the sum of these divisors is equal to 28, we call it a perfect number.
9+
10+
Interestingly the sum of the proper divisors of 220 is 284 and
11+
the sum of the proper divisors of 284 is 220, forming a chain of two numbers.
12+
For this reason, 220 and 284 are called an amicable pair.
13+
14+
Perhaps less well known are longer chains.
15+
For example, starting with 12496, we form a chain of five numbers:
16+
12496 -> 14288 -> 15472 -> 14536 -> 14264 (-> 12496 -> ...)
17+
18+
Since this chain returns to its starting point, it is called an amicable chain.
19+
20+
Find the smallest member of the longest amicable chain with
21+
no element exceeding one million.
22+
23+
Solution is doing the following:
24+
- Get relevant prime numbers
25+
- Iterate over product combination of prime numbers to generate all non-prime
26+
numbers up to max number, by keeping track of prime factors
27+
- Calculate the sum of factors for each number
28+
- Iterate over found some factors to find longest chain
29+
"""
30+
31+
from math import isqrt
32+
33+
34+
def generate_primes(max_num: int) -> list[int]:
35+
"""
36+
Calculates the list of primes up to and including `max_num`.
37+
38+
>>> generate_primes(6)
39+
[2, 3, 5]
40+
"""
41+
are_primes = [True] * (max_num + 1)
42+
are_primes[0] = are_primes[1] = False
43+
for i in range(2, isqrt(max_num) + 1):
44+
if are_primes[i]:
45+
for j in range(i * i, max_num + 1, i):
46+
are_primes[j] = False
47+
48+
return [prime for prime, is_prime in enumerate(are_primes) if is_prime]
49+
50+
51+
def multiply(
52+
chain: list[int],
53+
primes: list[int],
54+
min_prime_idx: int,
55+
prev_num: int,
56+
max_num: int,
57+
prev_sum: int,
58+
primes_degrees: dict[int, int],
59+
) -> None:
60+
"""
61+
Run over all prime combinations to generate non-prime numbers.
62+
63+
>>> chain = [0] * 3
64+
>>> primes_degrees = {}
65+
>>> multiply(
66+
... chain=chain,
67+
... primes=[2],
68+
... min_prime_idx=0,
69+
... prev_num=1,
70+
... max_num=2,
71+
... prev_sum=0,
72+
... primes_degrees=primes_degrees,
73+
... )
74+
>>> chain
75+
[0, 0, 1]
76+
>>> primes_degrees
77+
{2: 1}
78+
"""
79+
80+
min_prime = primes[min_prime_idx]
81+
num = prev_num * min_prime
82+
83+
min_prime_degree = primes_degrees.get(min_prime, 0)
84+
min_prime_degree += 1
85+
primes_degrees[min_prime] = min_prime_degree
86+
87+
new_sum = prev_sum * min_prime + (prev_sum + prev_num) * (min_prime - 1) // (
88+
min_prime**min_prime_degree - 1
89+
)
90+
chain[num] = new_sum
91+
92+
for prime_idx in range(min_prime_idx, len(primes)):
93+
if primes[prime_idx] * num > max_num:
94+
break
95+
96+
multiply(
97+
chain=chain,
98+
primes=primes,
99+
min_prime_idx=prime_idx,
100+
prev_num=num,
101+
max_num=max_num,
102+
prev_sum=new_sum,
103+
primes_degrees=primes_degrees.copy(),
104+
)
105+
106+
107+
def find_longest_chain(chain: list[int], max_num: int) -> int:
108+
"""
109+
Finds the smallest element of longest chain
110+
111+
>>> find_longest_chain(chain=[0, 0, 0, 0, 0, 0, 6], max_num=6)
112+
6
113+
"""
114+
115+
max_len = 0
116+
min_elem = 0
117+
for start in range(2, len(chain)):
118+
visited = {start}
119+
elem = chain[start]
120+
length = 1
121+
122+
while elem > 1 and elem <= max_num and elem not in visited:
123+
visited.add(elem)
124+
elem = chain[elem]
125+
length += 1
126+
127+
if elem == start and length > max_len:
128+
max_len = length
129+
min_elem = start
130+
131+
return min_elem
132+
133+
134+
def solution(max_num: int = 1000000) -> int:
135+
"""
136+
Runs the calculation for numbers <= `max_num`.
137+
138+
>>> solution(10)
139+
6
140+
>>> solution(200000)
141+
12496
142+
"""
143+
144+
primes = generate_primes(max_num)
145+
chain = [0] * (max_num + 1)
146+
for prime_idx, prime in enumerate(primes):
147+
if prime**2 > max_num:
148+
break
149+
150+
multiply(
151+
chain=chain,
152+
primes=primes,
153+
min_prime_idx=prime_idx,
154+
prev_num=1,
155+
max_num=max_num,
156+
prev_sum=0,
157+
primes_degrees={},
158+
)
159+
160+
return find_longest_chain(chain=chain, max_num=max_num)
161+
162+
163+
if __name__ == "__main__":
164+
print(f"{solution() = }")

0 commit comments

Comments
 (0)