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 propagation 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 - Empty puzzle string
17 - Words with no letters
18 - Single word equations
19 - Leading zero constraints
20 - No valid solutions
21 """
22 # Edge Case: Empty puzzle string
23 if not puzzle:
24 return None
25
26 # Parse the puzzle
27 # Edge Case: Invalid puzzle format (no '==')
28 if "==" not in puzzle:
29 return None
30
31 left_side, right_side = puzzle.split("==")
32 left_words = [word.strip() for word in left_side.split("+")]
33 right_word = right_side.strip()
34
35 # Edge Case: No words in equation
36 if not left_words or not right_word:
37 return None
38
39 # Extract all unique letters
40 letters = set()
41 for word in left_words + [right_word]:
42 letters.update(word)
43
44 # Edge Case: No letters in puzzle
45 if not letters:
46 return None
47
48 letters = list(letters)
49
50 # Identify first letters (can't be zero)
51 first_letters = {word[0] for word in left_words + [right_word]}
52
53 # Edge Case: Single word on left side
54 if len(left_words) == 1 and left_words[0] == right_word:
55 # This would mean A == A, which is trivially true but not a valid puzzle
56 return None
57
58 def word_to_number(word, mapping):
59 """Convert a word to a number using the given mapping."""
60 return int(''.join(str(mapping[char]) for char in word))
61
62 def is_valid_mapping(mapping):
63 """Check if the current mapping satisfies the equation."""
64 # Check for leading zeros
65 for word in left_words + [right_word]:
66 if len(word) > 1 and mapping[word[0]] == 0:
67 return False
68
69 left_sum = sum(word_to_number(word, mapping) for word in left_words)
70 right_value = word_to_number(right_word, mapping)
71 return left_sum == right_value
72
73 def backtrack(index, mapping, used_digits):
74 """Backtracking function to find a valid mapping."""
75 # Base case: all letters have been assigned
76 if index == len(letters):
77 if is_valid_mapping(mapping):
78 return mapping.copy()
79 return None
80
81 letter = letters[index]
82
83 # Try each digit from 0-9
84 for digit in range(10):
85 # Skip if digit is already used
86 if digit in used_digits:
87 continue
88
89 # Skip if this is a first letter and digit is 0
90 if letter in first_letters and digit == 0:
91 continue
92
93 # Assign the digit
94 mapping[letter] = digit
95 used_digits.add(digit)
96
97 # Recurse
98 result = backtrack(index + 1, mapping, used_digits)
99 if result is not None:
100 return result
101
102 # Backtrack
103 del mapping[letter]
104 used_digits.remove(digit)
105
106 return None
107
108 # Start backtracking
109 result = backtrack(0, {}, set())
110 return result
111
112# Handled Edge Cases:
113# - Invalid puzzle format (no '==')
114# - Empty puzzle string
115# - Words with no letters
116# - Single word equations
117# - Leading zero constraints
118# - No valid solutions
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.