# Resolution du Sudoku par Z3 : 81 entiers + Distinct sur lignes/colonnes/blocs.
# C'est le VRAI resolveur de production (le temoin = la grille solution).
from z3 import Solver, Int, Distinct, sat
import time
# Grille de test (puzzle classique, 0 = case vide)
PUZZLE = [
[5,3,0,0,7,0,0,0,0],
[6,0,0,1,9,5,0,0,0],
[0,9,8,0,0,0,0,6,0],
[8,0,0,0,6,0,0,0,3],
[4,0,0,8,0,3,0,0,1],
[7,0,0,0,2,0,0,0,6],
[0,6,0,0,0,0,2,8,0],
[0,0,0,4,1,9,0,0,5],
[0,0,0,0,8,0,0,7,9],
]
def resoudre_sudoku_z3(puzzle):
s = Solver()
X = [[Int(f"x_{r}_{c}") for c in range(9)] for r in range(9)]
for r in range(9):
for c in range(9):
s.add(X[r][c] >= 1, X[r][c] <= 9)
if puzzle[r][c] != 0:
s.add(X[r][c] == puzzle[r][c])
s.add(Distinct([X[r][c] for c in range(9)])) # ligne
for c in range(9):
s.add(Distinct([X[r][c] for r in range(9)])) # colonne
for br in range(0, 9, 3): # bloc 3x3
for bc in range(0, 9, 3):
s.add(Distinct([X[br+i][bc+j] for i in range(3) for j in range(3)]))
if s.check() != sat:
return None
m = s.model()
return [[m[X[r][c]].as_long() for c in range(9)] for r in range(9)]
t0 = time.perf_counter()
solution = resoudre_sudoku_z3(PUZZLE)
dt_z3 = time.perf_counter() - t0
print(f"Resolution Z3 : sat en {dt_z3*1000:.1f} ms")
print("Grille solution :")
for r in range(9):
print(" " + " ".join(str(solution[r][c]) for c in range(9)))
# Verification : chaque ligne doit passer la reconnaissance lookahead.
ok = all(LIGNE_VALIDE.match("".join(map(str, solution[r]))) for r in range(9))
print(f"Les {len(PUZZLE)} lignes passent la reconnaissance regex : {ok}")