Created
May 5, 2025 21:34
-
-
Save JustAnAverageGuy/c19b583c37c8725f3fb45957382abdda to your computer and use it in GitHub Desktop.
A z3 solver based solution for the unique sum sudoku puzzle by myxo and `I love sleeping`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# https://sudokupad.app/nLjQ8DjNRJ | |
# "Unique Sum Sudoku" | |
# By I Love Sleeping & Myxo | |
# Normal sudoku rules do NOT apply. Fill the grid with digits 1-9, such that no digit repeats in any row, column or box. The set of digits in each row or column is unique, eg. if a row contains the digits 1234, no other row or column may contain exactly those digits. The digits in every row, column and box sum to x, where x has to be determined by the solver. Digits separated by an X sum to 10. Digits separated by a V sum to 5. Not all Xs and Vs are necessarily given. | |
# Featured in video: "How Hard Can A 4x4 Sudoku Be?" - Cracking The Cryptic | |
# https://www.youtube.com/watch?v=JvbnQMzOHhI | |
from itertools import permutations | |
import z3 | |
solver = z3.Solver() | |
grid = [[z3.Int(f'g_{r}_{c}') for c in range(4)] for r in range(4)] | |
def transpose(grid): | |
return [[grid[j][i] for j in range(len(grid))] for i in range(len(grid[1]))] | |
def boxes(grid): | |
boxes = [[], [], [], []] | |
for row in range(4): | |
for col in range(4): | |
boxes[(row // 2) * 2 + (col // 2)].append(grid[row][col]) | |
return boxes | |
######## SUDOKU RULES ############### | |
for row in grid: | |
for var in row: | |
solver.add(0 < var, var < 10) | |
for row in grid: | |
solver.add(z3.Distinct(*row)) | |
for col in transpose(grid): | |
solver.add(z3.Distinct(*col)) | |
for box in boxes(grid): | |
solver.add(z3.Distinct(*box)) | |
######## SAME SUM CONSTRAINTS ######## | |
sum_X = z3.Sum(grid[0]) | |
for row in grid: | |
solver.add(z3.Sum(row) == sum_X) | |
for col in transpose(grid): | |
solver.add(z3.Sum(col) == sum_X) | |
for box in boxes(grid): | |
solver.add(z3.Sum(box) == sum_X) | |
######## SUM CONSTRAINTS ############# | |
sums = { | |
5: [ | |
((0, 0), (0, 1)), | |
((1, 2), (2, 2)), | |
], | |
10: [ | |
((0, 3), (1, 3)), | |
((1, 1), (1, 2)), | |
((2, 1), (3, 1)), | |
], | |
} | |
for sum, pairs in sums.items(): | |
for (r1, c1), (r2, c2) in pairs: | |
solver.add(grid[r1][c1] + grid[r2][c2] == sum) | |
######## SET DISTINCT ################ | |
def set_distinct(row1, row2): | |
return z3.Not( | |
z3.Or( | |
[ | |
z3.And([row1[i] == row2[p[i]] for i in range(4)]) | |
for p in permutations(range(4)) | |
] | |
) | |
) | |
rows_and_cols = [*grid, *transpose(grid)] | |
for i, el1 in enumerate(rows_and_cols): | |
for el2 in rows_and_cols[i + 1 :]: | |
solver.add(set_distinct(el1, el2)) | |
###################################### | |
def print_solution(m): | |
for row in grid: | |
print(*["%2s" % m.eval(ele) for ele in row]) | |
print(f'\nTotal sum: {m.eval(sum_X)}') | |
print('---- solving ----') | |
if solver.check() == z3.sat: | |
print('---- solved -----\n') | |
m = solver.model() | |
print_solution(m) | |
expressions = [] | |
for row in grid: | |
for ele in row: | |
expressions.append(ele != m.eval(ele)) | |
solver.add(z3.Or(expressions)) | |
if solver.check() == z3.unsat: | |
print('Solution is unique') | |
else: | |
print('Solution is not unique') | |
m = solver.model() | |
print_solution(m) | |
else: | |
print('IMPOSSIBLE') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Another video solution