@ngel

Finished
1from itertools import permutations
2
13def solve(puzzle: str) -> dict[str, int] | None:
2 pass
4 """
5 Solve an alphametics puzzle.
6
7 Args:
8 puzzle (str): A string representing the alphametics puzzle (e.g., "SEND + MORE == MONEY")
9
10 Returns:
11 dict[str, int] | None: A dictionary mapping letters to digits that solves the puzzle,
12 or None if no solution exists.
13
14 Example:
15 >>> solve("SEND + MORE == MONEY")
16 {'S': 9, 'E': 5, 'N': 6, 'D': 7, 'M': 1, 'O': 0, 'R': 8, 'Y': 2}
17 """
18 # Parse the puzzle
19 if "==" not in puzzle:
20 return None
21
22 left_side, right_side = puzzle.replace(" ", "").split("==")
23
24 # Edge Case: Empty puzzle parts
25 if not left_side or not right_side:
26 return None
27
28 # Determine the operator and split accordingly
29 if "+" in left_side:
30 operator = "+"
31 operands = left_side.split("+")
32 elif "-" in left_side:
33 operator = "-"
34 operands = left_side.split("-")
35 elif "*" in left_side:
36 operator = "*"
37 operands = left_side.split("*")
38 elif "/" in left_side:
39 operator = "/"
40 operands = left_side.split("/")
41 else:
42 # Single operand on left side (e.g., "A == B")
43 operator = None
44 operands = [left_side]
45
46 # Edge Case: No operands
47 if not operands or any(not operand for operand in operands):
48 return None
49
50 # Get all unique letters in the puzzle
51 letters = set("".join(operands) + right_side)
52
53 # Edge Case: More than 10 unique letters (impossible to assign unique digits)
54 if len(letters) > 10:
55 return None
56
57 # Get the first letter of each word (these can't be zero)
58 first_letters = {word[0] for word in operands + [right_side]}
59
60 # Try all possible digit assignments
61 digits = range(10)
62
63 # Edge Case: Not enough digits for all letters
64 if len(letters) > len(digits):
65 return None
66
67 for perm in permutations(digits, len(letters)):
68 mapping = dict(zip(letters, perm))
69
70 # Edge Case: Leading zeros
71 if any(mapping[letter] == 0 for letter in first_letters):
72 continue
73
74 # Convert words to numbers using the current mapping
75 try:
76 operand_values = [int("".join(str(mapping[c]) for c in operand)) for operand in operands]
77 result_value = int("".join(str(mapping[c]) for c in right_side))
78 except KeyError:
79 # This shouldn't happen with correct logic, but just in case
80 continue
81
82 # Check if the equation is correct based on the operator
83 if operator == "+":
84 if sum(operand_values) == result_value:
85 return mapping
86 elif operator == "-":
87 # For subtraction: first operand - second operand = result
88 if len(operand_values) == 2 and operand_values[0] - operand_values[1] == result_value:
89 return mapping
90 elif operator == "*":
91 # For multiplication: first operand * second operand = result
92 if len(operand_values) == 2 and operand_values[0] * operand_values[1] == result_value:
93 return mapping
94 elif operator == "/":
95 # For division: first operand / second operand = result (integer division)
96 if len(operand_values) == 2 and operand_values[1] != 0 and operand_values[0] // operand_values[1] == result_value and operand_values[0] % operand_values[1] == 0:
97 return mapping
98 elif operator is None:
99 # Single operand equals result
100 if len(operand_values) == 1 and operand_values[0] == result_value:
101 return mapping
102
103 # Edge Case: No solution found
104 return None
105
106# Handled Edge Cases: Empty puzzle parts, No addends, More than 10 unique letters, Not enough digits for all letters, Leading zeros, No solution found
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.