Skip to content

Commit 3349351

Browse files
committed
feat: add algorithm 95 to find the longest amicable chain
1 parent 1eee73b commit 3349351

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

project_euler/problem_095/sol1.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Project Euler Problem: https://projecteuler.net/problem=95
3+
4+
An amicable chain is a sequence of numbers where each number is the sum of the proper divisors of the previous one, and the chain eventually returns to the starting number.
5+
6+
The problem is to find the smallest member of the longest amicable chain under a given limit.
7+
8+
In this implementation, we aim to identify all amicable chains and find the one with the maximum length, while also returning the smallest member of that chain.
9+
"""
10+
11+
def sum_of_proper_divisors(n):
12+
"""Calculate the sum of proper divisors of n."""
13+
if n < 2:
14+
return 0 # Proper divisors of 0 and 1 are none.
15+
total = 1 # Start with 1, since it is a proper divisor of any n > 1
16+
sqrt_n = int(n**0.5) # Calculate the integer square root of n.
17+
18+
# Loop through possible divisors from 2 to the square root of n
19+
for i in range(2, sqrt_n + 1):
20+
if n % i == 0: # Check if i is a divisor of n
21+
total += i # Add the divisor
22+
if i != n // i: # Avoid adding the square root twice
23+
total += n // i # Add the corresponding divisor (n/i)
24+
25+
return total
26+
27+
def find_longest_amicable_chain(limit):
28+
"""Find the smallest member of the longest amicable chain under a given limit."""
29+
sum_divisors = {} # Dictionary to store the sum of proper divisors for each number
30+
for i in range(1, limit + 1):
31+
sum_divisors[i] = sum_of_proper_divisors(i) # Calculate and store sum of proper divisors
32+
33+
longest_chain = [] # To store the longest amicable chain found
34+
seen = {} # Dictionary to track numbers already processed
35+
36+
# Iterate through each number to find amicable chains
37+
for start in range(1, limit + 1):
38+
if start in seen: # Skip if this number is already processed
39+
continue
40+
41+
chain = [] # Initialize the current chain
42+
current = start # Start with the current number
43+
while current <= limit and current not in chain:
44+
chain.append(current) # Add the current number to the chain
45+
seen[current] = True # Mark this number as seen
46+
current = sum_divisors.get(current, 0) # Move to the next number in the chain
47+
48+
# Check if we form a cycle and validate the chain
49+
if current in chain and current != start:
50+
cycle_start_index = chain.index(current) # Find where the cycle starts
51+
if current in sum_divisors and sum_divisors[current] in chain:
52+
# This means we have a valid amicable chain
53+
chain = chain[cycle_start_index:] # Take only the cycle part
54+
if len(chain) > len(longest_chain):
55+
longest_chain = chain # Update longest chain if this one is longer
56+
57+
return min(longest_chain) if longest_chain else None # Return the smallest member of the longest chain
58+
59+
def solution():
60+
"""Return the smallest member of the longest amicable chain under one million."""
61+
return find_longest_amicable_chain(10**6)
62+
63+
if __name__ == "__main__":
64+
smallest_member = solution() # Call the solution function
65+
print(smallest_member) # Output the smallest member of the longest amicable chain

0 commit comments

Comments
 (0)