You would be hard pressed to find to find someone who has not heard of the popular logic game Sudoku. You are given a \(9 \times 9\) board of \(81\) squares logically separated into \(9\) columsn, \(9\) rows, and \(9\) \(3 \times 3\) subsquares. The goal is, given a board with some initial numbers filled in, fill in the rest of the board so that every column, row, and subsquare have all the digits from 1 to 9.
While solving Sudoku is a good brain exercise, we can go one further, write a program to solve Sudokus and henceforth consider all future Sudokus solved. To follow the first part, you should be familiar with basic programming (I’ll be using Java, but it naturally translates to other languages) and the concept of recursion. Part 2, where we introduce the Dancing Links algorithm for solving Sudoku might be slightly more challenging.
1 The simplest solver
Imagine the simplest most brute-force method of trying to solve a Sudoku puzzle. Take the first empty square, and try putting all the numbers from \(1\) to \(9\) into it one by one. Start off by putting a \(1\) there and move on to the next empty square. Repeat this until you fill in all the squares. At this point, you’ll have a fully filled board, but it might not be a correct board – you have a write a function that checks whether a fully filled Sudoku board is valid. If it is, congratulations, you found a solution! Otherwise, you have to go back (the correct term is backtrack), and try the next possible number in turn for the previously filled in square.
1.1 Simplest solver Java code
Here is how the above text translates to a piece of Java code. For the rest of this article we’ll use to the convention to use an int[][]
to represent a sudoku board, where a 0
represents an empty value, and numbers 1..9
represent their respective integers.
The above piece of code is a direct translation of the previous explanation. It goes like this:
- Fill the board completely with numbers
- Check if it’s a valid solution
- If there are still boards you haven’t looked at, go back to 1.
Essentially, this will perform a naive Depth-First-Search (DFS). If the initial board is completely empty, you will in theory go through all the \(9^{81}\) board combinations, which is just around the number of all particles in the universe. It will simply never finish! Even if the board is partially filled in with, say, 10 numbers, doing \(9^{71}\) computations will not finish either. Hence the code above is only of educational value, and worthless for solving an actual puzzle!
2 Smarter Sudoku Solving
Consider the naive solver we looked at in the previous section. The problem is that we spend a lot of time exploring solution subspaces that cannot contain a solution! Say you have a sudoku with a \(1\) at position \((1,1)\). Then any solution that tries to place a \(1\) anywhere in column 1
or row 1
cannot possibly be a valid solution since it violates the rules of sudoku.
Let’s modify our solving algorithm in the following way:
- If placing an integer \(n\) at position \((i,j)\) results in an invalid board state which can’t possibly lead to a solution, abandon this branch of computation.
- Modify the function that checks whether a board is valid to a function that checks whether placing a number \(n\) at position \((i,j)\) results in a valid board state.
The modified Java code:
This technique will suffice in solving any Sudoku puzzle reasonably fast.
2.1 Naive DFS Algorithm Benchmark
For benchmarking, Sudoku boards generated by qqwing were used. For each difficulty setting (simple, easy, intermediate, expert), 200 boards were randomly generated and then used for benchmarking.
The table below shows results (in milliseconds).
Simple | Easy | Intermediate | Expert | |
---|---|---|---|---|
Min | 0.07 | 0.11 | 0.28 | 0.24 |
Max | 2039.4 | 3112.6 | 3071.8 | 1326.9 |
Avg | 48.9 | 59.6 | 44.5 | 37.8 |
Std | 174.3 | 214.7 | 214.7 | 114.1 |
3 Dancing Links
Dancing Links is an algorithm by Knuth to solve exact cover problems (also called Algorithm X). An exact cover problem, for our purposes, is as follows: given a matrix of ones and zeros, select a subset \(S\) of the rows so that each column has exactly one \(1\) when looking at just the rows \(S\).
If you are interested, I really urge you to read the above linked paper. It might come as a surprise (given he is known for writing encyclopedias of computer science), Knuth is a surprisingly readable author and Dancing Links is based on a surprisingly simple idea regarding doubly-linked lists.
What’s important for us, is that a Sudoku puzzle can be trivially represented as an exact cover problem. Not only that, but many other problems, when suitably expressed as an exact cover problem (for example N-queens), can also be solved using Algorithm X. We just need to write Algorithm X once, and then for any problem in which we are interested in and which can be formulate as an exact cover problem, we just need to implement the translation.
If you are eager and want to have a look at the code on github, here you go.
3.1 Dancing Links Sudoku Benchmark
We use the same data-set for the benchmark of Dancing Links.
Simple | Easy | Intermediate | Expert | |
---|---|---|---|---|
Min | 0.86 | 0.85 | 0.76 | 0.75 |
Max | 13.71 | 3.34 | 2.95 | 2.54 |
Avg | 1.35 | 1.20 | 1.04 | 1.00 |
Std | 1.07 | 0.40 | 0.30 | 0.25 |
Solving a board takes on average around a millisecond. As a rough estimate, Dancing Links is around 30-50 faster (including the time to perform the translation and set-up of Dancing Links data structures).
You might be surprised that as the perceived difficulty from qqwing
increased, the solvers seem to perform better. My guess is that this is due to the harder problems presenting more constraints, which are difficult for humans to take into account all at once when solving a Sudoku and causing a lot of back-tracking, are actually easier for a computer – the more logical constraints there are for a fixed number of variables, the quicker a computer will be able to arrive at a solution. If you are interested, I explored a similar idea for the game Hashiwokakero in my masters thesis along a bunch of other things.
3.2 Dancing Links Code
Dancing Links is slightly more complex than a simple recursive Depth-First-Search. You won’t find a better way to understand it and implement it than by reading Knuth’s excellent paper.
If you would prefer to skip to implementation, I have made it available on github. The core of the algorithm is in DancingLinks.java
, while the Sudoku-specific implementations are in the rest of the files.
4 Conclusion
There is a large amount of optimizations you can do to our naive version to make it faster. They are Sudoku-specific though, and they will not outperform Dancing Links, and moreover cannot be reused for other combinatorial problems of interest. If you are interested in writing a Sudoku solver, I hence highly encourage you to read Knuth’s paper and, more importantly, implement the algorithm in your language of choice. If you have any questions or comments, please don’t hesitate to speak out in the comment section below.