| 1 | from itertools import permutations |
| 2 | import re |
| 3 | |
| 1 | 4 | def solve(puzzle: str) -> dict[str, int] | None: |
| 2 | | pass |
| 5 | # Parse the puzzle |
| 6 | # Remove spaces and split by '==' |
| 7 | parts = puzzle.replace(" ", "").replace("==", "=").split("=") |
| 8 | if len(parts) != 2: |
| 9 | return None |
| 10 | |
| 11 | right_side = parts[1] |
| 12 | left_expression = parts[0] |
| 13 | |
| 14 | # Extract all unique letters |
| 15 | letters = set(re.findall(r'[A-Z]', puzzle)) |
| 16 | if not letters: |
| 17 | return None |
| 18 | |
| 19 | # Edge Case: Too many unique letters for digit mapping (more than 10) |
| 20 | if len(letters) > 10: |
| 21 | return None |
| 22 | |
| 23 | # Parse the left side to identify operators and operands |
| 24 | # Handle +, -, * operations |
| 25 | operators = re.findall(r'[+*-]', left_expression) |
| 26 | operands = re.split(r'[+*-]', left_expression) |
| 27 | |
| 28 | |
| 29 | |
| 30 | # Find first letters of each word (cannot be zero for multi-digit numbers) |
| 31 | first_letters = {word[0] for word in operands + [right_side] if len(word) > 1} |
| 32 | |
| 33 | # Edge Case: Empty or invalid input strings |
| 34 | if not all(word.isalpha() and word.isupper() for word in operands + [right_side]): |
| 35 | return None |
| 36 | |
| 37 | # Try all permutations of digits for the unique letters |
| 38 | for perm in permutations(range(10), len(letters)): |
| 39 | mapping = dict(zip(letters, perm)) |
| 40 | |
| 41 | # Edge Case: Leading zeros in multi-digit numbers |
| 42 | if any(mapping[first_letter] == 0 for first_letter in first_letters): |
| 43 | continue |
| 44 | |
| 45 | # Convert words to numbers using the mapping |
| 46 | def word_to_number(word: str) -> int: |
| 47 | number = 0 |
| 48 | for char in word: |
| 49 | number = number * 10 + mapping[char] |
| 50 | return number |
| 51 | |
| 52 | # Calculate left side result based on operators |
| 53 | if not operators: |
| 54 | # Single operand case (e.g., "A == B") |
| 55 | left_result = word_to_number(operands[0]) |
| 56 | else: |
| 57 | # Multiple operands with operators |
| 58 | left_result = word_to_number(operands[0]) |
| 59 | for i, op in enumerate(operators): |
| 60 | next_value = word_to_number(operands[i + 1]) |
| 61 | if op == '+': |
| 62 | left_result += next_value |
| 63 | elif op == '-': |
| 64 | left_result -= next_value |
| 65 | elif op == '*': |
| 66 | left_result *= next_value |
| 67 | |
| 68 | # Calculate right side value |
| 69 | right_value = word_to_number(right_side) |
| 70 | |
| 71 | # Check if equation is satisfied |
| 72 | if left_result == right_value: |
| 73 | return mapping |
| 74 | |
| 75 | # Edge Case: No solution exists for given puzzle |
| 76 | return None |
| 77 | |
| 78 | # Handled Edge Cases: Too many unique letters (>10) for digit mapping, Empty or invalid input strings, Leading zeros in multi-digit numbers, No solution exists for given puzzle |