Why TDD makes a lot of sense for Sudoko

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:

  1. 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).
  2. The first step was to get the solver (“the outside”) correct. For this step, I mocked out the board
  3. 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.
  4. 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:

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:

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:

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

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.

Creative Commons License
Why TDD makes a lot of sense for Sudoko by Johannes Brodwall, unless otherwise expressly stated, is licensed under a Creative Commons Attribution 3.0 Unported License.

About Johannes Brodwall

Johannes is the director of software development for the MRM product company BrandMaster. In his spare time he likes to coach teams and developers on better coding, collaboration, planning and product understanding.
This entry was posted in Extreme Programming, Java, video. Bookmark the permalink.
  • Elliott

    TDD is awesome. We all know and love it. However there are lots of times when the a few minutes on google will give you a better start then setting out the first tests. Google first, then test, then code will give a good code base that implements the correct solution, not just one that passes tests.

    While this result is correct it is wildly more computationally expensive than it needs to be. Sudoku is an exact cover problem and should be solved with variations on the exact cover algorithms, if a backtracking solver is to be used.

    http://en.wikipedia.org/wiki/Dancing_Links
    http://www.ocf.berkeley.edu/~jchu/publicportal/
    http://www.sudocue.net/dancinglinks.htm

    On top of that looking at the code I am reasonably sure that the code will find a solution to invalid puzzles. Puzzles where there are more than one solution will return the first found result.

    Fitting the board into a smaller memory footprint will result in less cache invalidation and a rather large speed up. (Yea yeah it's pre-mature optimization but solving these are not as hard as getting the code great.)

    Try and make sure that nothing makes the assumption that the board is 9×9 there are lots of more advanced puzzles that are 16×16(they take forever but are a real accomplishment when solved.)

  • http://www.johannesbrodwall.com/ Johannes Brodwall

    Thanks for your comments, Elliott. You raise many valid points on how to make the solution better. I will be studying the dancing links information more, but my initial impression is that this is fairly difficult stuff.

    When it comes to the context of the kata, it's fast enough for it's purpose. I'd rather focus on making an algorithm that's easy to understand than one that's as fast as it could be. That being said, the unit tests constrain the design quite a bit, and it would be interesting to see how easy it is to optimize this solution.

    Generalizing to 16×16 boards (or some of the other possible board configurations) requires more thinking than simply replacing all “9”s with variables. This is the sort of premature generalization I try to avoid until I understand what problems I'm required to solve.

    If you're up to the task, I would love to see a screencast where you solve sudoku using Dancing Links, though!

  • aamonty

    Title:
    Tenga Japan Sex Toys | Rends | Fliphole Black Fliphole Eggs Masturbators
    Keywords:
    Tenga Fliplite, Flip Lite, Fliphole
    Black Fliphole, Deep Throat Cup, Rolling Head Cup, Fliplite Solid Black, Melty White Fliplite, Japanese Sex toys, Japan sex toys, Tenga Singapore, sex shop, sex toys, masturbators, Felliato, Men Sex toy, masturbate, Tenga Eggs, Tenga Egg Wavy, Tenga Egg Spider, Tenga Egg Wavy, Tenga Lotion, Tenga Real Lotion, Tenga Wild Lotion, Mild Lotion, Tenga Warmer, Tenga Sex toys, Tenga Japan, japnese sex toys, adult novelty, adult toys

    Link:
    http://www.kujotoys.com/
    Description:

    kujotoys.com is established with the objective of selling sex toys to consumers who may find it embarrassing or inconvenient to purchase one directly from the retail shop front. It is located in both in Japan and Singapore and will ship to ensure that the customers get the product at the fastest time possible. It offers different kinds of sex toys for you and you can get it very easily by communicating with this site.

    For more please visit: http://www.kujotoys.com/

  • Anonymous Guest

    Thanks for the very nice article. I didn’t understand the “all cases (unlike Norvig)” part. Norvig’s article says that his program was tested on 2 million examples and worked correctly on all of them (correctly failing to find a solution on the puzzles that in fact have no solution). Do you have examples of cases that his does wrong and yours does right? Also, what is the mean run time (and variance) or your program?

  • http://www.johannesbrodwall.com/ Johannes Brodwall

    My solution turned out to be a lot slower than I initially thought, taking up to 2 minutes for a complex sudoku. I made some initial very stupid mistakes after I wrote the text but before I published it that made me optimistic. I had to change the text after that.

    As I understand Norvig’s solution, it is a heuristic solution, which means that there are some (theoretic) cases it can miss. The solution I use (brute forcing) is complete, so it’s guaranteed to find a solution if there is one. I may have misread Norvig, though.

    I’ve considered whether to write a follow-up post where I refactor the solution for speed. Would you be interested in this?

  • Anonymous Guest

    You did misread Norvig. He describes his algorithm as “systematically try all possibilities until we hit one that works”. Thus his algorithm is almost the same as yours, except that (a) he uses heuristics to choose the variable ordering while you go in linear order, and (b) he is more aggressive about propagating constraints. The result is an algorithm that is complete, but is about 1000 times faster (and more compact in terms of lines of code, which some see as an advantage and some as a disadvantage). To the extent that your solution looks simpler, it is because it is simpler: it is doing so much less. You should get down to the .01 second per puzzle range before you can compare apples to apples.

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

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