#!/usr/bin/env groovy /** * Sudoku solver */ // the games to solve def games = [ " 1 5 7 48 2 2 9 7 3 29 5 4 68 1 8 1 4 6 28 5 1 4 ", " 897 9 1 6 1 9 3 2 574 1 6 8 2 7 5 9 763 ", "5 1 9 73 2 5 6 7 5 8 8 3 4 7 1 8 2 1 69 6 7 4", " 7 4 63 2 9 4 5 8 7 3 9 8 6 7 8 5 7 2 1 4 7 69 2 3 ", "57 9 18 3 4 8 6 24 5 6 95 5 9 3 2 91 3 75", " 7 4 63 2 9 4 5 8 7 3 9 8 6 7 8 5 7 2 1 4 7 69 2 3 ", "18 4 8 9 345 4 96 52 8 76 53 1 251 7 2 7 92", " 6 3 459 28 8 73 9 5 9 8 6 7 8 5 36 9 42 938 2 1 ", " 14 786 1 5 9 8 23 13 56 95 7 5 4 3 918 73 ", "7 5 2 3 35 4 7 6 3 82 62 9 3 8 9 41 1 7 3 2", " 1 74 2 966 3 57 89 93 51 69 27 6 582 7 52 8 ", " 73 2 3 18 62 734 5 5 849 67 42 6 9 43 " ] def solver = new Backtracker() solver.isSolution = { theGame -> def numbersSolved = 0 for (i in 0..<9) { for (j in 0..<9) { if (theGame.grid[i][j].value) numbersSolved++ } } return numbersSolved == 81 } solver.possibilities = { theGame -> playSingleCandidates(theGame) if (solver.isSolution(theGame)) { return [theGame] } else { def position = getPositionWithLowestNumberOfCandidates(theGame) def candidates = theGame.grid[position[0]][position[1]].candidates return candidates.collect{ def possibleGrid = new Grid(theGame) possibleGrid.play(it, position[0], position[1]) return possibleGrid } } } Grid playSingleCandidates(Grid game) { def player = { def counter = 0 for (i in 0..<9) { for (j in 0..<9) { if (game.grid[i][j].candidates.size() == 1) { game.play(game.grid[i][j].candidates[0], i, j) counter++ } } } return counter } while (true) { if (player() == 0) break } } List getPositionWithLowestNumberOfCandidates(Grid game) { def position = [0,0] def lowestNumberOfCandidates = 9 for (i in 0..<9) { for (j in 0..<9) { def cell = game.grid[i][j] if (cell.value == null && cell.candidates.size() < lowestNumberOfCandidates) { position = [i,j] lowestNumberOfCandidates = cell.candidates.size() } } } return position } games.each{ game -> def grid = new Grid() grid.loadGame(game) def start = System.currentTimeMillis() def solution = solver.solve(grid) if (solution) println solution.toString() else println "No solution found" println "\nSolved in ${System.currentTimeMillis() - start}ms with ${solver.stages} steps and ${solver.backtracks} backtracks" } class Cell implements Cloneable { def value def candidates = ['1', '2', '3', '4', '5', '6', '7', '8', '9'] Object clone() { def newCell = new Cell() newCell.value = value newCell.candidates = candidates.clone() return newCell } } class Grid { def grid = [[], [], [], [], [], [], [], [], []] Grid() { for(i in 0..<9) for (j in 0..<9) grid[i][j] = new Cell() } Grid(Grid g) { for(i in 0..<9) for (j in 0..<9) grid[i][j] = g.grid[i][j].clone() } void loadGame(String game) { def counter = 0 for(c in game) { if (c ==~ "[1-9]") play(c, counter.intdiv(9), counter % 9) counter++ } } void play(number, line, column) { def cell = grid[line][column] cell.value = number cell.candidates = [] removeCandidateFromLine([number], line) removeCandidateFromColumn([number], column) removeCandidateFromBlock([number], line, column) } private void removeCandidateFromLine(candidates, line) { for (i in 0..<9) grid[line][i].candidates = grid[line][i].candidates - candidates } private void removeCandidateFromColumn(candidates, column) { for (i in 0..<9) grid[i][column].candidates = grid[i][column].candidates - candidates } private void removeCandidateFromBlock(candidates, line, column) { for (i in 0..<3) for (j in 0..<3) { def x = i + 3*(line.intdiv(3)) def y = j + 3*(column.intdiv(3)) grid[x][y].candidates = grid[x][y].candidates - candidates } } String candidatesToString() { def sb = new StringBuffer() for(i in 0..<9) { sb << (i % 3 == 0 ? "\n+" + ("-" * 27 + "+") * 3 : "") + "\n" for (j in 0..<9) { if (j % 3 == 0) sb << "|" def cell = grid[i][j] sb << cell.candidates.join('').center(9) } sb << "|" } sb << "\n+" + ("-" * 27 + "+") * 3 + "\n" return sb.toString() } String toString() { def sb = new StringBuffer() for(i in 0..<9) { sb << (i % 3 == 0 ? "\n+---+---+---+\n" : "\n") for (j in 0..<9) { if (j % 3 == 0) sb << "|" def cell = grid[i][j] sb << (cell.value ? cell.value : " ") } sb << "|" } sb << "\n+---+---+---+\n" return sb.toString() } boolean equals(Object obj) { this.hashCode() == obj?.hashCode() } int hashCode() { def sb = new StringBuffer() for(i in 0..<9) for (j in 0..<9) sb << (grid[i][j].candidates + grid[i][j].value) return sb.toString().hashCode() } } class Backtracker { def possibilities def isSolution def stages = 0 def backtracks = 0 def solve(possibility) { def steps = [] doSolveRecurse(steps, possibility) } private doSolveRecurse(List steps, currentPossibility) { if (isSolution(currentPossibility)) { return currentPossibility } else { stages++ def allPossibilities = possibilities(currentPossibility) if (allPossibilities.size() > 0) { steps << currentPossibility } else { // we've backtracked all the way to the root, so there's no solution if (steps.size() <= 1) return null def popped = steps.pop() backtracks++ } for(possibility in allPossibilities) { def value = doSolveRecurse(steps, possibility) if (value != null) return value } } } }