Skip to content

Commit 9dd0d84

Browse files
committed
Add documentation and tests for the Euler project problem 95 solution.
1 parent 0c8cf8e commit 9dd0d84

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

Diff for: project_euler/problem_095/__init__.py

Whitespace-only changes.

Diff for: project_euler/problem_095/sol1.py

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""
2+
Project Euler Problem 95: https://projecteuler.net/problem=95
3+
4+
Amicable Chains
5+
6+
Solution is doing the following:
7+
- Get relevant prime numbers
8+
- Iterate over product combination of prime numbers to generate all non-prime
9+
numbers up to max number, by keeping track of prime factors
10+
- Calculate the sum of factors for each number
11+
- Iterate over found some factors to find longest chain
12+
13+
>>> solution(200000)
14+
12496
15+
16+
"""
17+
18+
from numpy import sqrt
19+
20+
21+
def sum_primes(factor_d, num):
22+
"""
23+
Calculates the sum of factors from all prime exponents.
24+
25+
>>> sum_primes({2: 1, 3: 1}, 6)
26+
6
27+
"""
28+
tot = 1
29+
for p in factor_d:
30+
comp = 0
31+
ex_factor = 1
32+
for _ in range(factor_d[p] + 1):
33+
comp += ex_factor
34+
ex_factor *= p
35+
tot *= comp
36+
return tot - num
37+
38+
39+
def generate_primes(n: int):
40+
"""
41+
Calculates the list of primes up to and including n.
42+
43+
>>> generate_primes(6)
44+
[2, 3, 5]
45+
"""
46+
primes = [True] * (n + 1)
47+
primes[0] = primes[1] = False
48+
for i in range(2, int(sqrt(n + 1)) + 1):
49+
if primes[i]:
50+
j = i * i
51+
while j <= n:
52+
primes[j] = False
53+
j += i
54+
primes_list = []
55+
for i in range(2, len(primes)):
56+
if primes[i]:
57+
primes_list += [i]
58+
return primes_list
59+
60+
61+
def multiply(chain, primes, prime, prev_n, n_max, prev_sum, primes_d):
62+
"""
63+
Run over all prime combinations to generate non-prime numbers.
64+
65+
>>> multiply([None] * 3, {2}, 2, 1, 2, 0, {})
66+
"""
67+
68+
number = prev_n * prime
69+
primes_d[prime] = primes_d.get(prime, 0) + 1
70+
if prev_n % prime != 0:
71+
new_sum = prev_sum * (prime + 1) + prev_n
72+
else:
73+
new_sum = sum_primes(primes_d, number)
74+
chain[number] = new_sum
75+
for p in primes:
76+
if p >= prime:
77+
number_n = p * number
78+
if number_n > n_max:
79+
break
80+
multiply(chain, primes, p, number, n_max, new_sum, primes_d.copy())
81+
82+
83+
def find_longest_chain(chain, n_max):
84+
"""
85+
Finds the smallest element and length of longest chain
86+
87+
>>> find_longest_chain([0, 0, 0, 0, 0, 0, 6], 6)
88+
(6, 1)
89+
"""
90+
91+
length_max = 0
92+
elem_max = 0
93+
for i in range(2, len(chain)):
94+
start = i
95+
length = 1
96+
el = chain[i]
97+
visited = {i}
98+
while el > 1 and el <= n_max and el not in visited:
99+
length += 1
100+
visited.add(el)
101+
el = chain[el]
102+
103+
if el == start and length > length_max:
104+
length_max = length
105+
elem_max = start
106+
107+
return elem_max, length_max
108+
109+
110+
def solution(n_max: int = 1000000) -> int:
111+
"""
112+
Runs the calculation for numbers <= n_max.
113+
114+
>>> solution(10)
115+
6
116+
"""
117+
118+
primes = generate_primes(n_max)
119+
chain = [0] * (n_max + 1)
120+
for p in primes:
121+
if p * p > n_max:
122+
break
123+
multiply(chain, primes, p, 1, n_max, 0, {})
124+
125+
chain_start, _ = find_longest_chain(chain, n_max)
126+
return chain_start
127+
128+
129+
if __name__ == "__main__":
130+
print(f"{solution() = }")

0 commit comments

Comments
 (0)