fortitude

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