Friday, January 14, 2022

Time for the obligatory Sudoku solver. Everybody else has one. They have fancy features like constraint solvers and backtrackers, but I'm contrary. Let's just use brute force.

Given a sudoku puzzle, we'll make one step towards solution by picking an empty cell and trying all possible values in it:

(defun children (sudoku)
  (multiple-value-bind (row column) (first-empty-cell sudoku)
    (and row column
         (map 'list (lambda (digit)
                      (place-digit sudoku row column digit))
              (possible-digits sudoku row column)))))
to solve the entire puzzle, we start with a population containing the single problem sudoku. Each iteration we replace the population of sudokus with their children.
(defun solve (sudoku)
  (labels ((lp (population)
            (let ((next-generation (mapcan #'children population)))
              (if (null next-generation)
                  population
                  (lp next-generation)))))
    (lp (list sudoku))))

Nothing clever here, just going through the combinations. This turns out to be enough, though. An extreme puzzle can take a few seconds and allocate a few hundred megabytes of storage, but a typical puzzle is solved quickly.

If you want to be a bit more clever, you can choose a better cell than the first empty one. A better cell would be one with fewer possible values to put in it. Although it's more expensive to compute the possible values for all 81 cells to find the fewest, it greatly reduces the population growth and ultimately speeds things up. But I don't need a sudoku solver at all, let alone an efficient one.