chris

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 with constraint checking to find a valid digit assignment
12 where each letter represents a unique digit, no leading digit is zero, and the equation holds.
13
14 Handled Edge Cases:
15 - Invalid puzzle format (no '==')
16 - Words with invalid characters
17 - Leading zeros in multi-digit words
18 - No valid solutions
19 - Empty or whitespace-only puzzle
20 """
21 # Edge Case: Empty or whitespace-only puzzle
22 if not puzzle or not puzzle.strip():
23 return None
24
25 # Parse the puzzle
26 # Edge Case: Invalid puzzle format (no '==')
27 if "==" not in puzzle:
28 return None
29
30 left_side, right_side = puzzle.split("==")
31 left_side = left_side.strip()
32 right_side = right_side.strip()
33
34 # Extract words (remove operators like +, -, etc.)
35 import re
36 words = re.findall(r'[A-Z]+', left_side + ' ' + right_side)
37
38 # Edge Case: No words found
39 if not words:
40 return None
41
42 # Check for invalid characters
43 # Edge Case: Words contain non-alphabetic characters
44 for word in words:
45 if not word.isalpha():
46 return None
47
48 # Get all unique letters
49 unique_letters = set(''.join(words))
50
51 # Edge Case: More than 10 unique letters (impossible to assign unique digits)
52 if len(unique_letters) > 10:
53 return None
54
55 # Get first letters of each word (can't be zero)
56 first_letters = {word[0] for word in words}
57
58 # Convert words to expressions for evaluation
59 left_expr = left_side.replace("+", " + ").replace("-", " - ")
60 right_expr = right_side
61
62 # Create a list of letters for consistent ordering
63 letters_list = list(unique_letters)
64
65 def is_valid_solution(assignment):
66 """Check if the current assignment is valid"""
67 # Edge Case: Leading zeros
68 for word in words:
69 if assignment[word[0]] == 0 and len(word) > 1:
70 return False
71
72 # Evaluate the expressions
73 left_val = left_expr
74 right_val = right_expr
75
76 for letter, digit in assignment.items():
77 left_val = left_val.replace(letter, str(digit))
78 right_val = right_val.replace(letter, str(digit))
79
80 try:
81 return eval(left_val) == eval(right_val)
82 except:
83 return False
84
85 def backtrack(index, assignment, used_digits):
86 """Backtracking function to find a solution"""
87 # Base case: all letters assigned
88 if index == len(letters_list):
89 if is_valid_solution(assignment):
90 return assignment.copy()
91 return None
92
93 letter = letters_list[index]
94
95 # Try each digit from 0-9
96 for digit in range(10):
97 # Skip if digit already used
98 if digit in used_digits:
99 continue
100
101 # Skip if this is a first letter and digit is 0
102 # Edge Case: Leading zero constraint
103 if letter in first_letters and digit == 0:
104 continue
105
106 # Make assignment
107 assignment[letter] = digit
108 used_digits.add(digit)
109
110 # Recurse
111 result = backtrack(index + 1, assignment, used_digits)
112 if result is not None:
113 return result
114
115 # Backtrack
116 del assignment[letter]
117 used_digits.remove(digit)
118
119 return None
120
121 # Start backtracking
122 solution = backtrack(0, {}, set())
123 return solution
124
125# Handled Edge Cases: Empty puzzle, invalid format, non-alphabetic characters, more than 10 unique letters, leading zeros, no solution
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.