Sodoku Solver

Sodoku Solver#

Let’s begin by developing a “contention map”, which shows, for each cell in an ⍵×⍵ Sudoku puzzle, those cells that occupy the same row, column or box:

                                    ⍝ CONTENTION MAP
3 3⍴⍳3×3                            ⍝ matrix of box numbers
1 2 3 4 5 6 7 8 9

If this expression is not clear, edit and resubmit part of it.

APL functions apply to everything to their right, so you can experiment by removing functions from the left of any expression before continuing with the script.

3/3 3⍴⍳3×3                          ⍝ 3-replicated (/) along rows
1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9
33/3 3⍴⍳3×3                        ⍝ 3-replicated (⌿) down columns
1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 4 4 4 5 5 5 6 6 6 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 7 7 7 8 8 8 9 9 9 7 7 7 8 8 8 9 9 9

Based on the above, the following function generates this matrix of box numbers for boxes of shape :

box  {/ ⍴⍳×}                ⍝ fn: box numbers
box 3                               ⍝ 3×3 boxes for 9×9 puzzle
1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 4 4 4 5 5 5 6 6 6 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 7 7 7 8 8 8 9 9 9 7 7 7 8 8 8 9 9 9

We’ll start with a smaller, 4×4 Sudoku:

box 2                               ⍝ 2×2 boxes for 4×4 puzzle
1 1 2 2 1 1 2 2 3 3 4 4 3 3 4 4

Primitive function () generates an -array of indices. For a matrix, each 2-vector is a row and column number:

 4 4                               ⍝ row and column numbers
┌───┬───┬───┬───┐ │1 1│1 2│1 3│1 4│ ├───┼───┼───┼───┤ │2 1│2 2│2 3│2 4│ ├───┼───┼───┼───┤ │3 1│3 2│3 3│3 4│ ├───┼───┼───┼───┤ │4 1│4 2│4 3│4 4│ └───┴───┴───┴───┘

Primitive operator each (¨) applies its operand function between corresponding items of its argument arrays.

Catenating (,) each row-col vector with its box number produces a matrix of row-col-box triples:

(4 4) ,¨ box 2                     ⍝ row, column and box numbers
┌─────┬─────┬─────┬─────┐ │1 1 1│1 2 1│1 3 2│1 4 2│ ├─────┼─────┼─────┼─────┤ │2 1 1│2 2 1│2 3 2│2 4 2│ ├─────┼─────┼─────┼─────┤ │3 1 3│3 2 3│3 3 4│3 4 4│ ├─────┼─────┼─────┼─────┤ │4 1 3│4 2 3│4 3 4│4 4 4│ └─────┴─────┴─────┴─────┘

For a square Sudoku puzzle, the box size is the square-root of the puzzle size.

In APL, the square-root of is expressed as to the power (*) one-half (÷2):

25 49 * ÷2                          ⍝ to the power reciprocal 2
5 7

… leading to the following function in which ⊃⍵*÷2 means the first () item of the square-root of the right argument ():

rcb  {(),¨box2}              ⍝ fn: row-col-box numbers
rcb 4 4                             ⍝ ... for a 4×4 sudoku puzzle
┌─────┬─────┬─────┬─────┐ │1 1 1│1 2 1│1 3 2│1 4 2│ ├─────┼─────┼─────┼─────┤ │2 1 1│2 2 1│2 3 2│2 4 2│ ├─────┼─────┼─────┼─────┤ │3 1 3│3 2 3│3 3 4│3 4 4│ ├─────┼─────┼─────┼─────┤ │4 1 3│4 2 3│4 3 4│4 4 4│ └─────┴─────┴─────┴─────┘

Contention occurs between cells that contain corresponding row, column or box numbers.

Here are the cells that share a row, column or box with the cell () in the second row, second column:

(rcb 4 4) = 2 2 1                  ⍝ cells that share row-col-box with 2 2
┌─────┬─────┬─────┬─────┐ │0 0 1│0 1 1│0 0 0│0 0 0│ ├─────┼─────┼─────┼─────┤ │1 0 1│1 1 1│1 0 0│1 0 0│ ├─────┼─────┼─────┼─────┤ │0 0 0│0 1 0│0 0 0│0 0 0│ ├─────┼─────┼─────┼─────┤ │0 0 0│0 1 0│0 0 0│0 0 0│ └─────┴─────┴─────┴─────┘

Each (¨) vector in the above, of which 1 is a member (), represents a row, column or box contention:

1 ¨ (rcb 4 4)=⊂2 2 1               ⍝ cells in contention with cell at 2 2
1 1 0 0 1 1 1 1 0 1 0 0 0 1 0 0

Outer-product (∘.) produces a rank 4 (4×4×4×4) array of all comparisons. As the leading axes of this 256-item array are displayed down the page, it is rather long:

1 ¨ (rcb 4 4) ∘.= rcb 4 4          ⍝ complete contention array
1 1 1 1 1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 1 0 0 0 1 0 0 0 1 0 0 1 1 1 1 0 0 1 1 0 0 1 0 0 0 1 0 1 1 1 1 0 0 1 1 0 0 0 1 0 0 0 1 1 1 0 0 1 1 1 1 1 0 0 0 1 0 0 0 1 1 0 0 1 1 1 1 0 1 0 0 0 1 0 0 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 1 1 1 1 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 0 0 0 1 0 0 0 1 0 1 1 1 1 0 0 1 1 0 0 0 1 0 0 0 1 1 1 1 1 0 0 1 1 1 0 0 0 1 0 0 0 1 1 0 0 1 1 1 1 0 1 0 0 0 1 0 0 1 1 0 0 1 1 1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 1 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1

Enclosing () the first two axes [⍳2] gives us a 4x4 map of 4×4 contention matrices.

[2] 1 ¨ (rcb 4 4) ∘.= rcb 4 4    ⍝ map of contention matrices
┌───────┬───────┬───────┬───────┐ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ ├───────┼───────┼───────┼───────┤ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ ├───────┼───────┼───────┼───────┤ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ ├───────┼───────┼───────┼───────┤ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ └───────┴───────┴───────┴───────┘

Abstracting this as a function of rcb 4 4:

{[2] 1¨ ∘.=} rcb 4 4           ⍝ contention map
┌───────┬───────┬───────┬───────┐ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ ├───────┼───────┼───────┼───────┤ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ ├───────┼───────┼───────┼───────┤ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ ├───────┼───────┼───────┼───────┤ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ └───────┴───────┴───────┴───────┘

and naming the function:

cmap  {[2] 1¨ ∘.=}            ⍝ fn: contention map for ⍵-puzzle
cmap rcb 4 4                        ⍝ contention matrices for 4 4 puzzle
┌───────┬───────┬───────┬───────┐ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ ├───────┼───────┼───────┼───────┤ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ ├───────┼───────┼───────┼───────┤ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ ├───────┼───────┼───────┼───────┤ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 0 0 0│0 1 0 0│0 0 1 0│0 0 0 1│ │1 1 0 0│1 1 0 0│0 0 1 1│0 0 1 1│ │1 1 1 1│1 1 1 1│1 1 1 1│1 1 1 1│ └───────┴───────┴───────┴───────┘

Now let’s turn our attention to searching for a solution. We use a breadth-first search technique:

                                    ⍝ Breadth-first search

Here is a small Sudoku puzzle to be solved:

s44  4 4 0 0 0 0  0 0 2 1  3 0 0 4  0 0 0 0      ⍝ 4×4 Sudoku puzzle
0 0 0 0 0 0 2 1 3 0 0 4 0 0 0 0

The shape () of s44 is the vector 4 4:

 s44                               ⍝ shape of matrix s44
4 4

Primitive index function () selects item(s) from an array. Starting (say) at row 1, column 1:

1 1cmap rcb s44                  ⍝ contention for cell at 1 1
1 1 1 1 1 1 0 0 1 0 0 0 1 0 0 0

The product (×) of this matrix with the current state shows which numbers may not be placed at 1 1:

s44 × 1 1cmap rcb s44            ⍝ number(s) excluded from posn 1 1
0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0

Excluding (~) these numbers (just the number 3 in this case) from all possible numbers (⍳4) reveals which numbers are still available for placing at 1 1:

(4) ~ s44×⊃1 1cmap rcb s44       ⍝ plays still available at 1 1
1 2 4

with function:

avl  {(⍳⊃⍴) ~ ×⊃cmap rcb }   ⍝ fn: available plays at ⍺ for state ⍵
1 1 avl s44                         ⍝ available plays at 1 1 in s44
1 2 4

We need a way to place a given number at a particular position in a given state matrix.

As this operation requires three arguments (number position state), it is convenient to code it as an operator with -number ⍺⍺-posn -state.

We can place the number in a given position with primitive function take ():

2 399                              ⍝ 99 extended downwards and rightwards
99 0 0 0 0 0
¯2 ¯399                            ⍝ 99 extended upwards and leftwards
0 0 0 0 0 99
4 4¯3 ¯399                        ⍝ 99 at 3 3 in empty 4 4 grid
0 0 0 0 0 0 0 0 0 0 99 0 0 0 0 0

… and adding the current state:

s44 + 4 4¯3 ¯399                  ⍝ 99 at 3 3 in s44
0 0 0 0 0 0 2 1 3 0 99 4 0 0 0 0

… giving us this “merge” operator:

at  {+()(-⍺⍺)}               ⍝ op: ⍺ at ⍺⍺ in ⍵
99 (3 3 at) s44                     ⍝ 99 at 3 3 in s44
0 0 0 0 0 0 2 1 3 0 99 4 0 0 0 0

Now, merging each (¨) available state:

1 2 4 (1 1 at)¨ s44                ⍝ next states of s44 at 1 1
┌───────┬───────┬───────┐ │1 0 0 0│2 0 0 0│4 0 0 0│ │0 0 2 1│0 0 2 1│0 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┘

Using our avl function to generate the vector of available plays:

(1 1 avl s44) (1 1 at)¨ s44        ⍝ next states of s44 at 1 1
┌───────┬───────┬───────┐ │1 0 0 0│2 0 0 0│4 0 0 0│ │0 0 2 1│0 0 2 1│0 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┘

… suggesting this function for a vector of available states:

nxt  {( avl )( at)¨}          ⍝ fn: next states of ⍵ at ⍺
1 1 nxt s44                         ⍝ next states of s44 at 1 1
┌───────┬───────┬───────┐ │1 0 0 0│2 0 0 0│4 0 0 0│ │0 0 2 1│0 0 2 1│0 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┘

For the following move, we find each possible next state for each of these states, choosing a second cell at which to play.

In this way, we start to explore the tree of possibilities, one level at a time.

Let’s continue from position 1 2. Notice that we bind () left argument 1 2 to function nxt, so that the whole of the vector constitutes the left argument for each (¨) application.

1 2nxt¨ 1 1 nxt s44                ⍝ two levels of tree-search
┌─────────────────────────┬─────────────────────────┬─────────────────────────┐ │┌───────┬───────┬───────┐│┌───────┬───────┬───────┐│┌───────┬───────┬───────┐│ ││1 2 0 0│1 3 0 0│1 4 0 0│││2 1 0 0│2 3 0 0│2 4 0 0│││4 1 0 0│4 2 0 0│4 3 0 0││ ││0 0 2 1│0 0 2 1│0 0 2 1│││0 0 2 1│0 0 2 1│0 0 2 1│││0 0 2 1│0 0 2 1│0 0 2 1││ ││3 0 0 4│3 0 0 4│3 0 0 4│││3 0 0 4│3 0 0 4│3 0 0 4│││3 0 0 4│3 0 0 4│3 0 0 4││ ││0 0 0 0│0 0 0 0│0 0 0 0│││0 0 0 0│0 0 0 0│0 0 0 0│││0 0 0 0│0 0 0 0│0 0 0 0││ │└───────┴───────┴───────┘│└───────┴───────┴───────┘│└───────┴───────┴───────┘│ └─────────────────────────┴─────────────────────────┴─────────────────────────┘

The primitive reduction operator (/), applied to a vector, has the effect of placing its left operand function between each item of its right argument, to produce a scalar result.

We can join the sub-vectors with a catenate (,) reduction (/) of the vector of vectors and disclosing () the resulting scalar:

⊃,/ 1 2nxt¨ 1 1 nxt s44            ⍝ two levels of tree-search
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │1 2 0 0│1 3 0 0│1 4 0 0│2 1 0 0│2 3 0 0│2 4 0 0│4 1 0 0│4 2 0 0│4 3 0 0│ │0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘

In order that the initial state is not a special case, it helps to start with a 1-vector by ravelling (,) the enclose () of the puzzle matrix:

⊃,/1 2nxt¨ ⊃,/1 1nxt¨ ,⊂s44       ⍝ 2-level search
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │1 2 0 0│1 3 0 0│1 4 0 0│2 1 0 0│2 3 0 0│2 4 0 0│4 1 0 0│4 2 0 0│4 3 0 0│ │0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│0 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘

Continuing from position 2 1:

⊃,/2 1nxt¨ ⊃,/1 2nxt¨ ⊃,/1 1nxt¨ ,⊂s44    ⍝ 3-level search
┌───────┬───────┬───────┬───────┐ │1 2 0 0│1 3 0 0│2 1 0 0│2 3 0 0│ │4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┴───────┘

Next, we extract the positions (2 1), (1 2) and (1 1) as left argument () to a containing function:

For example: ⊃,/1 2∘nxt¨...

becomes: 1 2{⊃,/⍺∘nxt¨⍵}...

nxtv  {⊃,/nxt¨ }                ⍝ fn: next state vector
2 1 nxtv 1 2 nxtv 1 1 nxtv ,⊂s44    ⍝ 3-level search
┌───────┬───────┬───────┬───────┐ │1 2 0 0│1 3 0 0│2 1 0 0│2 3 0 0│ │4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┴───────┘

“Factoring out” the nxtv function as a reduction (/), we recode the above 3-level search as:

nxtv/ (2 1)(1 2)(1 1)(,⊂s44)       ⍝ 3-level search
┌───────┬───────┬───────┬───────┐ │1 2 0 0│1 3 0 0│2 1 0 0│2 3 0 0│ │4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┴───────┘

Choosing cell (1 3) for our next step:

nxtv/ (1 3)(2 1)(1 2)(1 1)(,⊂s44)  ⍝ 4-level search
┌───────┬───────┬───────┬───────┬───────┬───────┐ │1 2 3 0│1 2 4 0│1 3 4 0│2 1 3 0│2 1 4 0│2 3 4 0│ │4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│0 0 0 0│ └───────┴───────┴───────┴───────┴───────┴───────┘

The order of selecting the cells for the search is unimportant. Let’s continue from (4 2):

nxtv/ (4 2)(1 3)(2 1)(1 2)(1 1)(,⊂s44)           ⍝ 5-level search
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │1 2 3 0│1 2 3 0│1 2 4 0│1 2 4 0│1 3 4 0│1 3 4 0│1 3 4 0│2 1 3 0│2 1 3 0│2 1 4 0│2 1 4 0│2 3 4 0│2 3 4 0│2 3 4 0│ │4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│ │3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│3 0 0 4│ │0 1 0 0│0 4 0 0│0 1 0 0│0 4 0 0│0 1 0 0│0 2 0 0│0 4 0 0│0 2 0 0│0 4 0 0│0 2 0 0│0 4 0 0│0 1 0 0│0 2 0 0│0 4 0 0│ └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘

… (3 2) …

nxtv/ (3 2)(4 2)(1 3)(2 1)(1 2)(1 1)(,⊂s44)      ⍝ 6-level search
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │1 2 3 0│1 2 4 0│1 3 4 0│1 3 4 0│1 3 4 0│1 3 4 0│2 1 3 0│2 1 4 0│2 3 4 0│2 3 4 0│2 3 4 0│2 3 4 0│ │4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│4 0 2 1│ │3 1 0 4│3 1 0 4│3 2 0 4│3 1 0 4│3 1 0 4│3 2 0 4│3 2 0 4│3 2 0 4│3 2 0 4│3 1 0 4│3 1 0 4│3 2 0 4│ │0 4 0 0│0 4 0 0│0 1 0 0│0 2 0 0│0 4 0 0│0 4 0 0│0 4 0 0│0 4 0 0│0 1 0 0│0 2 0 0│0 4 0 0│0 4 0 0│ └───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘

… and (1 4):

nxtv/ (1 4)(3 2)(4 2)(1 3)(2 1)(1 2)(1 1)(,⊂s44) ⍝ 7-level search
┌───────┬───────┐ │1 2 4 3│2 1 4 3│ │4 0 2 1│4 0 2 1│ │3 1 0 4│3 2 0 4│ │0 4 0 0│0 4 0 0│ └───────┴───────┘

You might like to take a moment to find the complete solution by extending the above list, one item at a time, using the remaining empty cell coordinates: (4 4) (4 3) (2 2) (3 3) (4 1).

For a complete search, we just need to supply our reduction with a vector of the positions of all empty cells.

Here is the matrix of all cell positions:

⍳⍴ s44                              ⍝ matrix of positions
┌───┬───┬───┬───┐ │1 1│1 2│1 3│1 4│ ├───┼───┼───┼───┤ │2 1│2 2│2 3│2 4│ ├───┼───┼───┼───┤ │3 1│3 2│3 3│3 4│ ├───┼───┼───┼───┤ │4 1│4 2│4 3│4 4│ └───┴───┴───┴───┘

of which, these are the empty ones:

s44=0                               ⍝ 1 → empty cell
1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 1

Primitive function ravel (,) returns a vector of the items of its argument array:

, ⍳⍴ s44                            ⍝ vector of positions
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │1 1│1 2│1 3│1 4│2 1│2 2│2 3│2 4│3 1│3 2│3 3│3 4│4 1│4 2│4 3│4 4│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
, s44=0                             ⍝ vector of empties
1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 1

Replicate (/) selects empty cells:

(,s44=0)/,⍳⍴s44                     ⍝ positions of empty cells
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │1 1│1 2│1 3│1 4│2 1│2 2│3 2│3 3│4 1│4 2│4 3│4 4│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

giving function:

emt  {(,=0)/,⍳⍴}                 ⍝ fn: empty cell positions in state ⍵
emt s44                             ⍝ empty cell positions in s44
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │1 1│1 2│1 3│1 4│2 1│2 2│3 2│3 3│4 1│4 2│4 3│4 4│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

We can now find a vector of complete states. In general, there may be many solutions although published Sudoku puzzles typically have only one.

nxtv/(emt s44),⊂,⊂s44              ⍝ vector of all solutions.
┌───────┐ │2 1 4 3│ │4 3 2 1│ │3 2 1 4│ │1 4 3 2│ └───────┘

Abstracting this as a function of the puzzle matrix s44:

svec  {nxtv/(emt ),⊂,⊂}         ⍝ fn: solution vector
svec s44                            ⍝ solution(s) for s44
┌───────┐ │2 1 4 3│ │4 3 2 1│ │3 2 1 4│ │1 4 3 2│ └───────┘

The following little function nests the boxes of a Sudoku state. A 9×9 puzzle will appear as a 3×3 matrix of 3×3 boxes:

(It is left as an “exercise for the student” to figure out how this works.)

sfmt{[3 4]1 3 2 4(2/()2)}  ⍝ pleasing format
sfmt s44                            ⍝ initial state and
┌───┬───┐ │0 0│0 0│ │0 0│2 1│ ├───┼───┤ │3 0│0 4│ │0 0│0 0│ └───┴───┘
sfmt svec s44                      ⍝ ... first (⊃) solution
┌───┬───┐ │2 1│4 3│ │4 3│2 1│ ├───┼───┤ │3 2│1 4│ │1 4│3 2│ └───┴───┘

The coding of svec is rather inefficient because it re-calculates cmap rcb at each step:

svec                                ⍝ svec calls nxtv for each free cell,
{⊃nxtv/(emt ⍵),⊂,⊂⍵}
nxtv                                ⍝ nxtv calls nxt for each state,
{⊃,/⍺∘nxt¨⍵}
nxt                                 ⍝ nxt calls avl and
{(⍺ avl ⍵)(⍺ at)¨⊂⍵}
avl                                 ⍝ avl calls cmap rcb ⍴⍵.
{(⍳⊃⍴⍵)~⍵×⊃⍺⌷cmap rcb⍴⍵}

We can arrange to calculate (cmap rcb ⍴⍵) only once and passing it as an additional operand from svec to nxtv to nxt to avl.

We do this by making avl, nxt and nxtv into operators, and receiving additional parameter (cmap rcb ⍴⍵) as operand ⍺⍺. Compare these re-codings with the original versions:

avl{(⍳⊃⍴)~×⊃⍺⍺}                ⍝ op avl: receives (cmap rcb ⍴⍵) as ⍺⍺ from
nxt{((⍺⍺ avl))( at)¨}         ⍝ op nxt: receives (cmap rcb ⍴⍵) as ⍺⍺ from
nxtv{⊃,/(⍺⍺ nxt)¨}              ⍝ op nxtv: receives (cmap rcb ⍴⍵) as ⍺⍺ from
svec{(⍺⍺ )nxtv/(emt ),⊂⊂}     ⍝ op svec: receives (cmap∘rcb) as operand ⍺⍺.

Binding () cmap with rcb as a function operand to svec:

sfmt  cmaprcb svec s44            ⍝ ... first (⊃) solution
┌───┬───┐ │2 1│4 3│ │4 3│2 1│ ├───┼───┤ │3 2│1 4│ │1 4│3 2│ └───┴───┘

Now we’re in a position to try a larger puzzle.

The following sequence specifies each row, starting with a 9×9 matrix of zeros:

s99  9 90                         ⍝ input of 9×9 puzzle ...
s99[1;]  0 0 1 6 9 0 5 0 0         ⍝ 1st row
s99[2;]  4 0 0 2 7 0 0 0 1         ⍝ 2nd row
s99[3;]  0 7 0 0 0 0 0 9 0         ⍝ 3rd row
s99[4;]  0 0 0 0 0 0 0 3 0         ⍝ 4th row
s99[5;]  0 0 0 4 3 0 0 0 7         ⍝ 5th row
s99[6;]  0 0 0 7 8 0 6 0 0         ⍝ 6th row
s99[7;]  0 0 6 0 0 0 8 0 5         ⍝ 7th row
s99[8;]  0 2 0 1 4 0 0 6 0         ⍝ 8th row
s99[9;]  0 1 0 3 5 0 0 4 0         ⍝ 9th row

Display of initial state of 9×9 puzzle:

sfmt s99                            ⍝ 9×9 sudoku puzzle
┌─────┬─────┬─────┐ │0 0 1│6 9 0│5 0 0│ │4 0 0│2 7 0│0 0 1│ │0 7 0│0 0 0│0 9 0│ ├─────┼─────┼─────┤ │0 0 0│0 0 0│0 3 0│ │0 0 0│4 3 0│0 0 7│ │0 0 0│7 8 0│6 0 0│ ├─────┼─────┼─────┤ │0 0 6│0 0 0│8 0 5│ │0 2 0│1 4 0│0 6 0│ │0 1 0│3 5 0│0 4 0│ └─────┴─────┴─────┘

Naming a function for the format of the first () solution:

sudoku  {sfmtcmaprcb svec }     ⍝ Sudoku solver
sudoku s44                          ⍝ first solution of smaller puzzle.
┌───┬───┐ │2 1│4 3│ │4 3│2 1│ ├───┼───┤ │3 2│1 4│ │1 4│3 2│ └───┴───┘
sudoku s99                          ⍝ first solution of larger puzzle
┌─────┬─────┬─────┐ │2 8 1│6 9 3│5 7 4│ │4 6 9│2 7 5│3 8 1│ │5 7 3│8 1 4│2 9 6│ ├─────┼─────┼─────┤ │7 9 2│5 6 1│4 3 8│ │6 5 8│4 3 9│1 2 7│ │1 3 4│7 8 2│6 5 9│ ├─────┼─────┼─────┤ │3 4 6│9 2 7│8 1 5│ │9 2 5│1 4 8│7 6 3│ │8 1 7│3 5 6│9 4 2│ └─────┴─────┴─────┘

A Generalisation#

APL makes it easy to extend functions to arrays of higher rank.

To demonstrate this, let’s convert our code to solve a 3D variant of Sudoku.

                                    ⍝ 3D Sudoku
s333  3 3 30                      ⍝ input of 3×3×3 puzzle
s333[1;;]  3 3   0 0 0  8 1 0  0 0 2   ⍝ 1st plane
s333[2;;]  3 3   0 6 0  0 7 0  9 0 4   ⍝ 2nd plane
s333[3;;]  3 3   5 0 0  0 0 3  0 0 0   ⍝ 3rd plane
s333                                ⍝ 3D Sudoku puzzle
0 0 0 8 1 0 0 0 2 0 6 0 0 7 0 9 0 4 5 0 0 0 0 3 0 0 0

We choose slightly different contention conditions: there are no “boxes” but numbers must not be repeated in any of the nine x, y, or z planes.

First, we generalise our cmap function:

cmap                                ⍝ 2D-specific cmap
{⊂[⍳2]1∊¨⍵∘.=⍵}

to enclose () an appropriate number of axes [].

This is the rank (⍴⍴ the shape of the shape) of the argument array:

cmap  {[⍳⍴⍴]1¨∘.=}            ⍝ rank-invariant cmap

Then, using primitive function () in place of the rcb function, as there are no boxes:

sudoku3D  {cmap svec }         ⍝ 3D Sudoku solver

Finally, we must generalise avl to return available numbers for a subarray.

For 2D Sudoku this will continue to be the numbers for a row or column vector and for the 3D version it will be those for a whole plane:

avl                                 ⍝ 2D-specific avl
∇avl
avl  {(⍳×/1↓⍴)~×⊃⍺⍺}           ⍝ rank-invariant avl
  1 1 ((cmap rcb s99 )avl)  s99    ⍝ 2D plays at 1 1 for s99
2 3 8
1 1 1 ((cmap    s333)avl) s333    ⍝ 3D plays at 1 1 1 for s333
3 4 7

Our 2D Sudoku solver continues to work as before:

sudoku s99                          ⍝ 2D solution
┌─────┬─────┬─────┐ │2 8 1│6 9 3│5 7 4│ │4 6 9│2 7 5│3 8 1│ │5 7 3│8 1 4│2 9 6│ ├─────┼─────┼─────┤ │7 9 2│5 6 1│4 3 8│ │6 5 8│4 3 9│1 2 7│ │1 3 4│7 8 2│6 5 9│ ├─────┼─────┼─────┤ │3 4 6│9 2 7│8 1 5│ │9 2 5│1 4 8│7 6 3│ │8 1 7│3 5 6│9 4 2│ └─────┴─────┴─────┘

and our 3D solver solves the 3D puzzle s333:

sudoku3D s333                       ⍝ 3D solution
3 4 9 8 1 6 7 5 2 1 6 8 2 7 5 9 3 4 5 2 7 4 9 3 6 8 1

Notice how few changes were needed to generalise our program for a higher dimensional puzzle.

Conclusion#

There are many ways to solve Sudoku. The method used here illustrates a Pure Functional Programming approach.

The code we have developed consists only of the application of functions and operators to their arguments and operands. In particular, it contains:

No explicit representation of state (no variables).

No explicit control structures (no conditionals, loops, etc).

]defs sudoku -t
sudoku ← {sfmt⊃cmap∘rcb svec ⍵} sfmt ← {⊂[3 4]1 3 2 4⍉(2/(⍴⍵)*÷2)⍴⍵} cmap ← {⊂[⍳⍴⍴⍵]1∊¨⍵∘.=⍵} rcb ← {(⍳⍵),¨box⊃⍵*÷2} box ← {⍵⌿⍵/⍵ ⍵⍴⍳⍵×⍵} svec ← {⊃(⍺⍺⍴⍵)nxtv/(emt ⍵),⊂⊂⍵} nxtv ← {⊃,/⍺∘(⍺⍺ nxt)¨⍵} nxt ← {(⍺(⍺⍺ avl)⍵)(⍺ at)¨⊂⍵} avl ← {(⍳×/1↓⍴⍵)~⍵×⊃⍺⌷⍺⍺} at ← {⍵+(⍴⍵)↑(-⍺⍺)↑⍺} emt ← {(,⍵=0)/,⍳⍴⍵}

For alternative codings of Sudoku in APL, see dfns.dyalog.com/n_sudoku.htm.

Credits#

Algorithm: Arthur Whitney Inspiration: Roger Hui