armor

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 The function uses backtracking to try different digit assignments for letters, respecting:
12 - Each letter maps to a unique digit
13 - Leading digits cannot be zero
14 - The mathematical equation must be satisfied
15 """
16 # Edge Case: Empty or None puzzle
17 if not puzzle:
18 return None
19
20 # Parse the puzzle
21 parts = puzzle.replace("==", "=").replace(" ", "").split("=")
22
23 # Edge Case: Invalid puzzle format (no equals sign or too many parts)
24 if len(parts) != 2:
25 return None
26
27 left_side, right_side = parts[0], parts[1]
28
29 # Split the left side by '+' to get addends
30 addends = left_side.split("+")
31
32 # Edge Case: No addends on the left side
33 if not addends or not all(addends):
34 return None
35
36 # Edge Case: Empty right side
37 if not right_side:
38 return None
39
40 # Extract all unique letters
41 letters = set()
42 for part in addends + [right_side]:
43 letters.update(part)
44
45 # Edge Case: No letters in puzzle
46 if not letters:
47 return None
48
49 # Identify first letters (can't be zero)
50 first_letters = {word[0] for word in addends + [right_side]}
51
52 # Edge Case: More than 10 unique letters (impossible to solve)
53 if len(letters) > 10:
54 return None
55
56 # Convert to list for indexing
57 letters_list = list(letters)
58
59 def is_valid_solution(mapping):
60 """Check if the current mapping satisfies the puzzle equation."""
61 # Check for leading zeros
62 for word in addends + [right_side]:
63 if mapping[word[0]] == 0 and len(word) > 1:
64 return False
65
66 # Calculate left side sum
67 left_sum = 0
68 for word in addends:
69 num = 0
70 for char in word:
71 num = num * 10 + mapping[char]
72 left_sum += num
73
74 # Calculate right side value
75 right_num = 0
76 for char in right_side:
77 right_num = right_num * 10 + mapping[char]
78
79 return left_sum == right_num
80
81 def backtrack(index, used_digits, mapping):
82 """Backtracking function to try digit assignments."""
83 # Base case: all letters have been assigned
84 if index == len(letters_list):
85 if is_valid_solution(mapping):
86 return mapping.copy()
87 return None
88
89 letter = letters_list[index]
90
91 # Try each digit from 0 to 9
92 for digit in range(10):
93 # Skip if digit is already used
94 if digit in used_digits:
95 continue
96
97 # Skip if this is a first letter and digit is 0
98 if letter in first_letters and digit == 0:
99 continue
100
101 # Try this digit assignment
102 mapping[letter] = digit
103 used_digits.add(digit)
104
105 # Recurse to next letter
106 result = backtrack(index + 1, used_digits, mapping)
107 if result is not None:
108 return result
109
110 # Backtrack
111 used_digits.remove(digit)
112 del mapping[letter]
113
114 return None
115
116 # Start backtracking
117 solution = backtrack(0, set(), {})
118 return solution
119 # Handled Edge Cases: Empty/None puzzle, invalid format, no addends, empty right side,
120 # no letters, more than 10 unique letters, leading zeros, mathematical inequality
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.