My colleague Thomas sent me a very interesting link about attempts to solve Sudoku using test-driven development. The article, somewhat unfairly, pits Ron Jeffries’ explorations of Sudoku using test-driven development against Peter Norvig’s “design driven” approach.

I found both attempts lacking. However, while Ron Jeffries freely admitted that he didn’t even know the rules of Sudoku when he started, both Norvig himself and his readers fawn over his solution. I didn’t find it very understandable.

So I took it upon myself to examine the problem myself. I did some up-front thinking in the shower and on the subway, then attacked the problem with TDD. I ended up with a solution that works in *all* cases (unlike Norvig). My implementation has readable code, readable tests, and solves the problem reasonably fast.

### Observations and conjectures

Here are a few things I learned from the exercise:

- When you’re using TDD to solve a tricky algorithm, you have to think about both the algorithm and the test approach.
- Solving a problem with a known algorithm using TDD gives more readable code than I otherwise would expect.
- When I solved the problem with TDD, running the solution on real problems worked the very first time I tried it.
- The trick to making TDD work is to work from the outside in.
- When creating a Sudoku solver, don’t think like a human! Think like a machine! The human algorithm is difficult to understand and likely to not work on all problems. This was the biggest problem with Norvig’s code

### The journey

I decided on the following approach:

- I had decided upon an initial design with a solver class and a board class. The solver should use a recursive depth first search. The solver asks the board what options exists per cell, but it has no knowledge of the rules of Sudoku (such as no duplicate numbers on the same row).
- The first step was to get the solver (“the outside”) correct. For this step, I mocked out the board
- The second step was to implement the interface that the solver needed for the board. Mainly, this is a matter of specifying the rules for what numbers can occur in which cell on a Sudoku board.
- Finally, I wrote some code to read and write the Sudoku board. When trying the solver on real problems, it worked the first time, and solved 95 hard problems correct. It was somewhat slow, though.

After solving the problem the first time, I practices a few times and recorded a screen cast of the solution:

### The solver

Testing the solver is a matter of creating a mock board and ensuring that the solver does the correct things. This is the most complex test case:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Test public void shouldBacktrackWhenNoMoreOptions() throws Exception { SudokuSolver solver = new SudokuSolver(); SudokuBoard board = mock(SudokuBoard.class); when(board.getOptionsForCell(anyInt(), anyInt())) .thenReturn(singleOption()); when(board.getOptionsForCell(8, 7)) .thenReturn(moreOptions(1, 2)); when(board.getOptionsForCell(8, 8)) .thenReturn(noOptions()) .thenReturn(singleOption()); assertThat(solver.findSolution(board)).isTrue(); InOrder order = inOrder(board); order.verify(board).setValueInCell(1, 8,7); order.verify(board).setValueInCell(2, 8,7); } |

It specifies that all cells, except (8,7) and (8,8) return exactly one option. (8,7) returns two options. (8,8) returns no options the first time it is called, and one option the second time. The test verifies that a solution is found, and the solver tries to set both options for (8,7).

This drives a rather simple algorithm. Here’s basically the whole algorithm:

1 2 3 4 5 6 7 8 9 10 11 12 |
public boolean findSolution(Board board, int cell) { if (cell == SIZE*SIZE) return true; boolean wasEmpty = board.isEmpty(row(cell), col(cell)); for (Integer value : board.getCellOptions(row(cell), col(cell))) { board.setValueInCell(value, row(cell), col(cell)); if (findSolution(board, cell+1)) return true; } if (wasEmpty) board.clearValueInCell(row(cell), col(cell)); return false; } |

The algorithm tries all available options for a cell in order. If no solution works for the rest of the board, the algorithm returns false (for “no solution”).

The algorithm is not how a human would solve Sudoku. But then again, we’re not writing a tutorial on how to solve Sudoku, we’re writing a *program* that solves Sudoku.

The board

As I implemented the solver, the interface for the board started to emerge. At that point in time, I had to create tests for the Sudoku board itself. A typical test verifies that the board doesn’t allow duplicate values in a row:

1 2 3 4 5 6 7 8 9 |
@Test public void shouldDisallowOptionsInSameRow() throws Exception { int row = 4; board.setValueInCell(1, row, 5); board.setValueInCell(2, row, 8); board.setValueInCell(3, row+1, 5); assertThat(board.getOptionsForCell(row, 0)) .excludes(1,2).contains(3); } |

The essence of SudokuBoard is finding out what values are legal in an open cell:

1 2 3 4 5 6 7 8 |
public List getOptionsForCell(int row, int col) { if (!isEmpty(row,col)) return Arrays.asList(cells[row][col]); List result = allOptions(); removeAllInRow(result, row); removeAllInCol(result, col); removeAllInBox(result, row, col); return result; } |

### TDD as a design guide

I invite you to compare Peter Norvig’s solution to mine (you can find the full source code for my solution in my github repository).

It would probably have been possible for me to code the solution faster without tests, but it probably would not have worked the first time I tried it. I also would have much less confidence in the code. Finally, I think the design imposed by the tests made my code easier to understand.

Copyright © 2010 Johannes Brodwall. All Rights Reserved.

Pingback: [translation] solve all the problems (sudoku)()

Pingback: Más Python, creando un programa para resolver cualquier sudoku | CyberHades()