A Sudoku is a 9 × 9 grid split into nine 3 × 3 boxes, with some digits already written in. Fill every empty square with a digit from 1 to 9 so that each row, each column and each box has every digit exactly once. That is the whole rule. Newspapers print one every day, and a hard one can keep a person busy for an hour, rubbing out guesses with a pencil.
The board below has a computer solver built in. Its first puzzle is one the Finnish mathematician Arto Inkala made in 2010 and called the most difficult puzzle he had ever created.
Before you press it: how long do you think the computer will take over Inkala’s puzzle?
Pick an answer to see why.
You need lists in Python (Grade 9) and functions (Grade 8). A function that calls itself is explained here; Grade 11’s Recursion has the whole story.
Try it
Rows and columns are numbered from 0 to 8 on this page, top to bottom and left to right, the way Python counts. Start with an easy puzzle and go slowly.
Watch what the solver does, and what the sentence under the board says.
- It always fills the first empty square, reading along the rows like a book.
- In each square it puts the smallest digit that fits: one not already in that square’s row, column or box.
- Sometimes it reaches a square where nothing fits. Then it takes back the digit it put in last (an undo, the square flashes red) and tries that square’s next digit. If that square has no digits left either, it takes back the one before, and so on.
- When it puts a digit in a square that had two or more choices, the board counts a guess.
Which square next?
The easy puzzle took 2,428 tries and 586 guesses, although it is easy. A person solving it would never guess: they would look for a square where only one digit fits. The solver could do that too. Instead of the first empty square, it could choose the square with the fewest digits allowed.
On the easy puzzle, choosing the square with the fewest choices first, how many guesses did the solver make?
Pick an answer to see why.
How it works: try, and take it back
The solver’s method has a name: backtracking. Choose a square, try a digit that fits, and go on to the next square as if that digit were right. If you are stuck later, come back (backtrack) and try the next digit. Because it only ever takes back its most recent choice, and goes through each square’s digits in order, it never tries the same thing twice and never misses a possibility: if the puzzle has a solution, it finds it.
The easiest way to write this is a function that calls itself. solve(grid) finds an empty square and, for each digit that fits there, calls solve on the grid with that digit put in. That call does the same for the next square, and so on. A call that finds no empty square has solved the puzzle and gives back True, and each call above it gives back True too. A call whose square has no digit left that works gives back False, and the call that made it tries its next digit. A function that calls itself is recursive.
Here is a small Sudoku to watch, 4 × 4 with 2 × 2 boxes and digits 1 to 4. The top two rows are full. The six empty squares are numbered as the program numbers them, 0 to 15 along the rows.
The program keeps the grid as one string of 16 characters, with . for an empty square. Step through its calls: each circle is one call of solve, with the digit it has just put in; ✓ or ✗ under it is what that call gave back.
In the tree, the first digit tried was 1 in square 8, and everything under it failed. What did that ✗ tell the program?
Pick an answer to see why.
Program it
You will build the solver in three steps, each running here on the page. A grid is a list of 9 rows, each a list of 9 numbers, with 0 for an empty square: grid[r][c] is the square in row r, column c.
Step 1: which digits fit?
allowed(grid, r, c) collects the digits already in the square’s row, column and box in a set, and gives back the digits from 1 to 9 that aren’t in it. The box starts at the row and column r // 3 * 3 and c // 3 * 3: for r = 5, that is row 3, the top of the middle band.
puzzle = ["...76....", ".......63", "1...2579.", "...9..87.", ".2..4..1.", ".81..3...", ".7619...8", "51.......", "....74..."] def read(rows): return [[0 if ch == '.' else int(ch) for ch in row] for row in rows] def allowed(grid, r, c): used = set(grid[r]) # the digits in its row for i in range(9): used.add(grid[i][c]) # ... in its column br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) # ... in its box return [d for d in range(1, 10) if d not in used] grid = read(puzzle)print(allowed(grid, 0, 0))print(allowed(grid, 2, 8))Output
[2, 3, 4, 8, 9][4]The top-left square has five choices; row 2, column 8 has only one, 4. (Tap those squares on the board above to check.)
Step 2: a backtracking solver
choose_square gives back the first empty square, or None when there is none. solve is the tree’s solve, for a 9 × 9 grid of lists: put a digit in, call itself, and if that fails, put the 0 back. It counts every digit it puts in, in tries, a global variable (one outside the function, which global tries lets it change).
puzzles = { 'easy': ["...76....", ".......63", "1...2579.", "...9..87.", ".2..4..1.", ".81..3...", ".7619...8", "51.......", "....74..."], 'Inkala 2010': ["..53.....", "8......2.", ".7..1.5..", "4....53..", ".1..7...6", "..32...8.", ".6.5....9", "..4....3.", ".....97.."],} def read(rows): return [[0 if ch == '.' else int(ch) for ch in row] for row in rows] def allowed(grid, r, c): used = set(grid[r]) for i in range(9): used.add(grid[i][c]) br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) return [d for d in range(1, 10) if d not in used] def choose_square(grid): for r in range(9): for c in range(9): if grid[r][c] == 0: return (r, c) return None tries = 0 def solve(grid): global tries square = choose_square(grid) if square is None: return True # no empty square: solved r, c = square for d in allowed(grid, r, c): grid[r][c] = d tries = tries + 1 if solve(grid): return True grid[r][c] = 0 # it failed: take it back return False for name, rows in puzzles.items(): tries = 0 grid = read(rows) solve(grid) print(f'{name}: {tries} tries') print(''.join(str(d) for d in grid[0]), '(the top row)')Output
easy: 2428 tries243769581 (the top row)Inkala 2010: 10007 tries145327698 (the top row)The same counts as the board: 2,428 tries on the easy puzzle and 10,007 on Inkala’s. Your program and the board use exactly the same method.
Step 3: the fewest choices first
Now the one change. choose_square looks at every empty square and gives back the one with the fewest digits allowed (the first one, if several tie). Nothing else changes.
puzzles = { 'easy': ["...76....", ".......63", "1...2579.", "...9..87.", ".2..4..1.", ".81..3...", ".7619...8", "51.......", "....74..."], 'Inkala 2010': ["..53.....", "8......2.", ".7..1.5..", "4....53..", ".1..7...6", "..32...8.", ".6.5....9", "..4....3.", ".....97.."],} def read(rows): return [[0 if ch == '.' else int(ch) for ch in row] for row in rows] def allowed(grid, r, c): used = set(grid[r]) for i in range(9): used.add(grid[i][c]) br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) return [d for d in range(1, 10) if d not in used] def choose_square(grid): best = None fewest = 10 for r in range(9): for c in range(9): if grid[r][c] == 0: n = len(allowed(grid, r, c)) if n < fewest: best = (r, c) fewest = n return best tries = 0 def solve(grid): global tries square = choose_square(grid) if square is None: return True r, c = square for d in allowed(grid, r, c): grid[r][c] = d tries = tries + 1 if solve(grid): return True grid[r][c] = 0 return False for name, rows in puzzles.items(): tries = 0 grid = read(rows) solve(grid) print(f'{name}: {tries} tries')Output
easy: 54 triesInkala 2010: 542 triesFrom 2,428 to 54, and from 10,007 to 542. Choosing each square takes more work (it counts the choices in every empty square), so each try is slower; but there are so many fewer tries that the whole search still finishes sooner, and on the harder puzzles below, far sooner.
The board below runs your choose_square: the one in the program just above, as you have it (press Edit there to change it). The page does the rest of the search exactly as solve does, and draws every step.
On the 17-clue puzzle, the in-order solver made 9,727,396 tries. About how many times more than yours?
Pick an answer to see why.
What your program shows
This program solves all four puzzles with your Step 3 solver and counts its guesses too: the squares it chose that had two or more digits allowed.
puzzles = { 'easy': ["...76....", ".......63", "1...2579.", "...9..87.", ".2..4..1.", ".81..3...", ".7619...8", "51.......", "....74..."], 'Inkala 2010': ["..53.....", "8......2.", ".7..1.5..", "4....53..", ".1..7...6", "..32...8.", ".6.5....9", "..4....3.", ".....97.."], 'Inkala 2006': ["85...24..", "72......9", "..4......", "...1.7..2", "3.5...9..", ".4.......", "....8..7.", ".17......", "....36.4."], '17 clues': ["4.....8.5", ".3.......", "...7.....", ".2.....6.", "....8.4..", "....1....", "...6.3.7.", "5..2.....", "1.4......"],} def read(rows): return [[0 if ch == '.' else int(ch) for ch in row] for row in rows] def allowed(grid, r, c): used = set(grid[r]) for i in range(9): used.add(grid[i][c]) br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) return [d for d in range(1, 10) if d not in used] def choose_square(grid): best = None fewest = 10 for r in range(9): for c in range(9): if grid[r][c] == 0: n = len(allowed(grid, r, c)) if n < fewest: best = (r, c) fewest = n return best tries = 0guesses = 0 def solve(grid): global tries, guesses square = choose_square(grid) if square is None: return True r, c = square digits = allowed(grid, r, c) if len(digits) > 1: guesses = guesses + 1 for d in digits: grid[r][c] = d tries = tries + 1 if solve(grid): return True grid[r][c] = 0 return False for name, rows in puzzles.items(): tries = 0 guesses = 0 grid = read(rows) clues = sum(1 for row in grid for d in row if d) solve(grid) print(f'{name:12} {clues} clues, {tries:5} tries, {guesses:3} guesses')Output
easy 27 clues, 54 tries, 0 guessesInkala 2010 23 clues, 542 tries, 28 guessesInkala 2006 22 clues, 4036 tries, 370 guesses17 clues 17 clues, 481 tries, 68 guessesYour program, with the board’s counts in order beside it, has found three things.
- a.Every puzzle falls. Even Inkala’s 2006 puzzle, which he called “the most difficult sudoku-puzzle known so far”, took your solver 4,036 tries: on a laptop a quarter of a second of plain Python, on the board a few thousandths. A person can spend hours on it; the program never gets tired of trying.
- b.The order of the search matters more than its speed. The same tries and undos, in a better order, cut the work from 9,727,396 tries to 481 on the 17-clue puzzle. A computer a thousand times faster wouldn’t do as much.
- c.The hard puzzles are the ones that need guesses. The easy puzzle never needs one: some square always has exactly one digit left. Inkala’s puzzles make even the best-ordered search guess dozens or hundreds of times (28 and 370), and a person can’t guess and rub out that often. That is what makes them hard for people: no square is forced, and you must think several moves ahead.
Watch the grid fill
This program records the grid after every fourth try while it solves Inkala 2010, and shows them as pictures, one square for each square of the grid and a color for each digit, the clues darker. pixels is this page’s own module for showing pictures from Python.
from pixels import show_frames rows = ["..53.....", "8......2.", ".7..1.5..", "4....53..", ".1..7...6", "..32...8.", ".6.5....9", "..4....3.", ".....97.."] def read(rows): return [[0 if ch == '.' else int(ch) for ch in row] for row in rows] def allowed(grid, r, c): used = set(grid[r]) for i in range(9): used.add(grid[i][c]) br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) return [d for d in range(1, 10) if d not in used] def choose_square(grid): best = None fewest = 10 for r in range(9): for c in range(9): if grid[r][c] == 0: n = len(allowed(grid, r, c)) if n < fewest: best = (r, c) fewest = n return best COLORS = ['white', '#d95f5f', '#e08d3c', '#d9b43c', '#7fb04a', '#3f9e7c', '#3f8fc0', '#5a6fc8', '#9a62c0', '#c85f9a']palette = {d: COLORS[d] for d in range(10)}for d in range(1, 10): # a clue: the same color, darker palette[10 + d] = tuple(int(COLORS[d][k:k + 2], 16) * 6 // 10 for k in (1, 3, 5)) clue = read(rows)frames = []tries = 0 def picture(grid): return [[grid[r][c] + (10 if clue[r][c] else 0) for c in range(9)] for r in range(9)] def solve(grid): global tries square = choose_square(grid) if square is None: return True r, c = square for d in allowed(grid, r, c): grid[r][c] = d tries = tries + 1 if tries % 4 == 0: frames.append(picture(grid)) if solve(grid): return True grid[r][c] = 0 return False grid = read(rows)solve(grid)frames.append(picture(grid))print(tries, 'tries,', len(frames), 'pictures')show_frames(frames, palette)Output
542 tries, 136 picturesPicture
Play it. The colors creep in, vanish in a flash when a guess goes wrong, and creep in again. In the last picture every row and column has all nine colors.
Sixteen clues are never enough
The 17-clue puzzle has only one solution. Take one clue away, and it has more than one. This program takes away the 6 in row 3, column 7, searches for solutions, stops when it has found 2, and prints the squares where those two differ.
rows = ["4.....8.5", ".3.......", "...7.....", ".2.....6.", "....8.4..", "....1....", "...6.3.7.", "5..2.....", "1.4......"] def read(rows): return [[0 if ch == '.' else int(ch) for ch in row] for row in rows] def allowed(grid, r, c): used = set(grid[r]) for i in range(9): used.add(grid[i][c]) br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) return [d for d in range(1, 10) if d not in used] def choose_square(grid): best = None fewest = 10 for r in range(9): for c in range(9): if grid[r][c] == 0: n = len(allowed(grid, r, c)) if n < fewest: best = (r, c) fewest = n return best found = [] def search(grid): square = choose_square(grid) if square is None: found.append([row[:] for row in grid]) # a copy: the search goes on return r, c = square for d in allowed(grid, r, c): grid[r][c] = d search(grid) grid[r][c] = 0 if len(found) == 2: return grid = read(rows)grid[3][7] = 0 # take away one cluesearch(grid)print(len(found), 'solutions')a, b = foundfor r in range(9): for c in range(9): if a[r][c] != b[r][c]: print(f'row {r}, column {c}: {a[r][c]} or {b[r][c]}')Output
2 solutionsrow 5, column 6: 3 or 6row 5, column 8: 6 or 3row 8, column 6: 6 or 3row 8, column 8: 3 or 6In these two solutions, four squares at the corners of a rectangle hold 3 and 6 one way round or the other, and nothing else in the grid tells the two ways apart. And these are only the first two the search met. Let the search run on instead of stopping at 2, and it finds every solution: 24,914 of them. (That run takes about 8 seconds of plain Python on our laptop, longer than this page lets a program run, so it isn’t done here.) One clue fewer, and the puzzle falls apart. Every one of the 17 clues is needed: take away any one of them instead, and the search finds hundreds or thousands of solutions.
So is there any 16-clue puzzle with only one solution? That question took mathematicians years.
Going further still
How many 4 × 4 grids?
Fill an empty 4 × 4 grid in every possible way instead of stopping at the first: there are only 288 finished 4 × 4 grids.
Dancing links
Your choose_square follows a rule older than Sudoku. In 1965 Solomon Golomb and Leonard Baumert suggested that a backtracking program choose, at each step, the choice with the fewest branches, whenever that can be found quickly. In 2000 Donald Knuth used the same rule in Algorithm X, his name for plain trying and taking back on puzzles where every part must be covered exactly once (an exact cover: tiling a board with shapes, placing queens on a chessboard, and Sudoku, where every square, and every digit in every row, column and box, must be used exactly once). His way of taking a step back quickly, dancing links, unhooks an item from a list so that it can be hooked back in exactly where it was. He traced that trick to Hirosi Hitotumatu and Kohei Noshita, who had used it in 1979 to make a program for the queens puzzle nearly twice as fast.
Thinking ahead
In his 2006 essay Solving Every Sudoku Puzzle, Peter Norvig adds constraint propagation to the search: after each digit, cross it off the choices of every square in its row, column and box, and whenever a square is left with one choice, fill it at once, and so on. Then most puzzles need little or no search at all. His program solved Inkala’s puzzles in about a hundredth of a second each. Yet among a million random puzzles he made, one took it 188 seconds. His guess was that a wrong choice in two early squares led to a long search before the program discovered it was wrong; that puzzle also turned out to have more than one solution.
A question to think about: on a 9 × 9 board your program is fast. Sudoku can be played on bigger boards too: 16 × 16 with 4 × 4 boxes, 25 × 25 with 5 × 5 boxes, and so on. In 2003 Takayuki Yato and Takahiro Seta, at the University of Tokyo, proved that deciding whether a Sudoku of any size can be filled in is NP-complete, one of the problems in Grade 12’s P versus NP: a filled grid is quick to check, and nobody knows a method that fills big ones quickly every time. Why does that not stop your program solving 9 × 9 puzzles in a blink?
Challenges
Three challenges, each checked on the page: write your function in the program above the checker (press Edit), then press Check my program. The examples show what your function should give; there are more tests you can’t see.
★ Is it solved?
Write is_solved(grid): True if the grid is full and every row, column and box holds each digit from 1 to 9 exactly once, False otherwise. Checking a finished grid is quick, however long finding it took.
def is_solved(grid): # Give back True if every row, column and box holds 1 to 9. return False grid = [[2, 4, 3, 7, 6, 9, 5, 8, 1], [9, 5, 7, 4, 8, 1, 2, 6, 3], [1, 6, 8, 3, 2, 5, 7, 9, 4], [6, 3, 4, 9, 1, 2, 8, 7, 5], [7, 2, 5, 8, 4, 6, 3, 1, 9], [9, 8, 1, 5, 7, 3, 6, 4, 2], [4, 7, 6, 1, 9, 2, 1, 5, 8], [5, 1, 9, 2, 3, 8, 4, 2, 7], [3, 9, 2, 6, 7, 4, 1, 5, 6]]print(is_solved(grid))★★ Only one choice
Write fill_forced(grid): again and again, find every empty square where only one digit is allowed and fill it, until there are none; then give back how many squares you filled. (Change grid itself.) Easy puzzles are solved this way alone, with no search at all.
def allowed(grid, r, c): used = set(grid[r]) for i in range(9): used.add(grid[i][c]) br = r // 3 * 3 bc = c // 3 * 3 for i in range(br, br + 3): for j in range(bc, bc + 3): used.add(grid[i][j]) return [d for d in range(1, 10) if d not in used] def fill_forced(grid): # Fill every square with one choice, again and again; give back how many. return 0 rows = ["...76....", ".......63", "1...2579.", "...9..87.", ".2..4..1.", ".81..3...", ".7619...8", "51.......", "....74..."]grid = [[0 if ch == '.' else int(ch) for ch in row] for row in rows]print(fill_forced(grid))★★★ One solution, or more?
Write count_solutions(grid, most): the number of solutions the puzzle has, but stop counting once you reach most. With most = 2 this tells you whether a puzzle is a proper Sudoku (exactly one solution). Don’t change grid in the end: put back every digit you try.
def count_solutions(grid, most): # Count the solutions, stopping at most. return 0 rows = ["4.....8.5", ".3.......", "...7.....", ".2.....6.", "....8.4..", "....1....", "...6.3.7.", "5..2.....", "1.4......"]grid = [[0 if ch == '.' else int(ch) for ch in row] for row in rows]print(count_solutions(grid, 2))Questions
What does a backtracking solver do when it reaches an empty square where no digit fits?
Pick an answer to see why.
Why does choosing the square with the fewest choices first make the solver so much faster?
Pick an answer to see why.
In Step 2, solve puts grid[r][c] = 0 after a failed call. A friend leaves the line out, and their solver says False for a puzzle that has a solution. Explain why.
Answer
A puzzle has a unique solution. The in-order solver made 9,727,396 tries on it, yours 481. Explain how two programs following the same rules can differ so much, using the idea of a tree of choices.
Answer
McGuire, Tugemann and Civario proved there is no 16-clue Sudoku with one solution. Why couldn’t they prove it simply by trying lots of 16-clue puzzles and failing to find one?
Answer
A friend says: “Sudoku is NP-complete, so a computer can’t solve them quickly.” Is that right for the puzzles in newspapers?
Answer
Where to go next
- Grade 11, Recursion: functions that call themselves, from the Tower of Hanoi to flood fill, and how to be sure they stop.
- Grade 12, The Limits of Computing: P versus NP, and why checking an answer can be so much easier than finding one.
- Grade 12, Algorithms for Hard Problems: another search whose choices multiply, and heuristics that make it manageable.
- How Can a Computer Never Lose at Tic-Tac-Toe?: a search through every future of a game, with a function that calls itself.
