weekends

Finished
11def solve(puzzle: str) -> dict[str, int] | None:
2 pass
2 """
3 Solve an alphametics puzzle.
4
5 Args:
6 puzzle: A string representing the alphametics puzzle (e.g., "SEND + MORE == MONEY")
7
8 Returns:
9 A dictionary mapping letters to digits that solves the puzzle, or None if no solution exists.
10
11 Example:
12 >>> solve("SEND + MORE == MONEY")
13 {'S': 9, 'E': 5, 'N': 6, 'D': 7, 'M': 1, 'O': 0, 'R': 8, 'Y': 2}
14 """
15 # Edge Case: Empty or None puzzle
16 if not puzzle:
17 return None
18
19 # Parse the puzzle
20 parts = puzzle.replace("==", "=").replace(" ", "").split("=")
21
22 # Edge Case: Invalid puzzle format (no equals sign or too many parts)
23 if len(parts) != 2:
24 return None
25
26 right_side = parts[1]
27 left_parts = parts[0].replace("+", " ").split()
28
29 # Edge Case: No terms on the left side
30 if not left_parts:
31 return None
32
33 # Extract all unique letters
34 letters = set()
35 for part in left_parts + [right_side]:
36 letters.update(part)
37
38 # Edge Case: No letters in puzzle
39 if not letters:
40 return None
41
42 # Extract first letters (can't be zero)
43 first_letters = {word[0] for word in left_parts + [right_side]}
44
45 # Convert to sorted list for consistent ordering
46 letters = list(letters)
47
48 # Edge Case: More than 10 unique letters (impossible to solve)
49 if len(letters) > 10:
50 return None
51
52 # Generate all possible digit assignments
53 def backtrack(index, used_digits, letter_to_digit):
54 """
55 Recursively try digit assignments using backtracking.
56
57 Args:
58 index: Current index in the letters list
59 used_digits: Set of digits already assigned
60 letter_to_digit: Dictionary mapping letters to digits
61
62 Returns:
63 Solution dictionary if found, else None
64 """
65 # Base case: all letters assigned
66 if index == len(letters):
67 # Check if the equation is satisfied
68 left_value = 0
69 for word in left_parts:
70 num = 0
71 for char in word:
72 num = num * 10 + letter_to_digit[char]
73 left_value += num
74
75 right_value = 0
76 for char in right_side:
77 right_value = right_value * 10 + letter_to_digit[char]
78
79 if left_value == right_value:
80 return letter_to_digit.copy()
81 return None
82
83 # Try assigning each unused digit to the current letter
84 letter = letters[index]
85
86 # Edge Case: First letter of a word cannot be zero
87 if letter in first_letters:
88 start_digit = 1
89 else:
90 start_digit = 0
91
92 for digit in range(start_digit, 10):
93 if digit not in used_digits:
94 # Make the assignment
95 letter_to_digit[letter] = digit
96 used_digits.add(digit)
97
98 # Recurse to the next letter
99 result = backtrack(index + 1, used_digits, letter_to_digit)
100 if result is not None:
101 return result
102
103 # Backtrack
104 used_digits.remove(digit)
105 del letter_to_digit[letter]
106
107 return None
108
109 # Start the backtracking search
110 solution = backtrack(0, set(), {})
111 return solution
112 # Handled Edge Cases: Empty/None puzzle, invalid format, no terms on left side,
113 # no letters, more than 10 unique letters, first letter being zero
Test NameStatus
test_leading_zero_solution_is_invalid
Pass
test_puzzle_with_eight_letters
Pass
test_puzzle_with_four_letters
Pass
test_puzzle_with_seven_letters
Pass
test_puzzle_with_six_letters
Pass
test_puzzle_with_ten_letters
Pass
test_puzzle_with_ten_letters_and_199_addends
Pass
test_puzzle_with_three_letters
Pass
test_puzzle_with_two_digits_final_carry
Pass
test_solution_must_have_unique_value_for_each_letter
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.