Added boardState section to the post
commit
347a23be41
|
@ -3,7 +3,7 @@ layout: post
|
|||
title: "Sudoku and Haskell: A Sudoku Solver"
|
||||
date: 2013-03-09 23:35
|
||||
comments: true
|
||||
categories: programming haskell sudoku puzzle
|
||||
categories: programming haskell sudoku
|
||||
published: false
|
||||
---
|
||||
|
||||
|
@ -13,6 +13,9 @@ sub-grids (called blocks) have every digit from 1 to 9. The puzzle starts with s
|
|||
and the player has to fill the rest to reach the solution. Since each unit (column, row or block) has
|
||||
9 cells and has to be filled with all 9 digits - 1 to 9 - there cannot be any duplicates in a unit.
|
||||
|
||||
{% img https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png 200 A typical Sudoku puzzle %}
|
||||
{% img https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Sudoku-by-L2G-20050714_solution.svg/250px-Sudoku-by-L2G-20050714_solution.svg.png 200 The same puzzle with solution numbers marked in red %}
|
||||
|
||||
In this post we look at how to solve a Sudoku with Haskell.
|
||||
|
||||
<!-- more -->
|
||||
|
@ -29,11 +32,12 @@ import qualified Data.Map as M
|
|||
import Data.Char (digitToInt, intToDigit)
|
||||
import Data.List (foldl', intersperse, intercalate)
|
||||
import Data.List.Split (chunksOf)
|
||||
import Data.Map ((!))
|
||||
import Control.Monad (foldM, guard)
|
||||
```
|
||||
|
||||
|
||||
Basic Setup
|
||||
Basic setup
|
||||
-----------
|
||||
|
||||
Now that the imports are out of the way let's setup the basic functionalities.
|
||||
|
@ -45,6 +49,8 @@ data Digit = ONE | TWO | TRE | FOR | FIV | SIX | SVN | EGT | NIN
|
|||
instance Show Digit where
|
||||
show digit = show $ fromEnum digit + 1
|
||||
|
||||
allDigits = S.fromList [ONE .. NIN]
|
||||
|
||||
data Cell = Cell { cellIdx :: Int, cellVals :: S.Set Digit }
|
||||
deriving (Eq)
|
||||
|
||||
|
@ -54,7 +60,7 @@ instance Show Cell where
|
|||
type Board = M.Map Int Cell
|
||||
|
||||
emptyBoard :: Board
|
||||
emptyBoard = foldl' (\m i -> M.insert i (Cell i $ S.fromList [ONE .. NIN]) m)
|
||||
emptyBoard = foldl' (\m i -> M.insert i (Cell i allDigits) m)
|
||||
M.empty [0 .. 80]
|
||||
```
|
||||
|
||||
|
@ -73,7 +79,7 @@ simple list of `Cell`s.
|
|||
`emptyBoard` creates an empty board, with all the cells, unfilled by folding over all the
|
||||
index list and inserting a cell with all possible digits in the map corresponding to each index.
|
||||
|
||||
Reading and Printing the Sudoku
|
||||
Reading and printing the Sudoku
|
||||
-------------------------------
|
||||
|
||||
Next let's write some functions to read a Sudoku board from a string and to print a board so that we
|
||||
|
@ -92,7 +98,7 @@ readBoard str = do
|
|||
foldM (\board (i, chr) -> do
|
||||
guard $ chr == '.' || (chr `S.member` S.fromList ['1' .. '9'])
|
||||
let cellVals = if chr == '.'
|
||||
then S.fromList [ONE .. NIN]
|
||||
then allDigits
|
||||
else S.singleton $ toEnum $ digitToInt chr - 1
|
||||
return $ M.insert i (Cell i cellVals) board)
|
||||
emptyBoard
|
||||
|
@ -133,14 +139,14 @@ and outputting the digit if the cell is filled else `.`.
|
|||
so by taking the output of `showBoard`, breaking it into chunks corresponding to rows and blocks,
|
||||
inserting spaces and `|` at appropriate places and then joining them with the borders made of `-`.
|
||||
|
||||
Here is an example run in ghci:
|
||||
Here is an example run in _ghci_:
|
||||
|
||||
<pre>
|
||||
*Sudoku> let boardStr = "6..3.2....4.....1..........7.26............543.........8.15........4.2........7.."
|
||||
*Sudoku> let (Just board) = readBoard boardStr
|
||||
*Sudoku> showBoard board
|
||||
"6..3.2....4.....1..........7.26............543.........8.15........4.2........7.."
|
||||
*Sudoku> putStr (prettyShowBoard board)
|
||||
*Sudoku> putStr (asciiShowBoard board)
|
||||
+-------+-------+-------+
|
||||
| 6 . . | 3 . 2 | . . . |
|
||||
| . 4 . | . . . | . 1 . |
|
||||
|
@ -156,9 +162,145 @@ Here is an example run in ghci:
|
|||
+-------+-------+-------+
|
||||
</pre>
|
||||
|
||||
The post can be downloaded as a compilable Literate Haskell file [here][2]. The Haskell code in the
|
||||
post can be downloaded [here][3].
|
||||
Is it solved yet?
|
||||
--------------------
|
||||
|
||||
Before we proceed to write a full-fledged Sudoku solver, it is good to have a function which tells us
|
||||
whether a board is filled completely and whether that solution is a valid one.
|
||||
|
||||
```haskell
|
||||
data BoardState = SOLVED | INCOMPLETE | INVALID
|
||||
deriving (Eq, Show)
|
||||
|
||||
boardState :: Board -> BoardState
|
||||
boardState board
|
||||
| any (\Cell{..} -> S.size cellVals /= 1) $ M.elems board = INCOMPLETE
|
||||
| any isUnitInvalid units = INVALID
|
||||
| otherwise = SOLVED
|
||||
where
|
||||
isUnitInvalid unitCells =
|
||||
(S.fromList . map (head . S.toList . cellVals) $ unitCells) /= allDigits
|
||||
|
||||
units = map (map (board !)) unitIxs
|
||||
|
||||
unitIxs = rowIxs ++ columnIxs ++ blockIxs
|
||||
rowIxs = map (\i -> [i * 9 .. i * 9 + 8]) [0..8]
|
||||
columnIxs = map (\i -> take 9 [i, i + 9 ..]) [0..8]
|
||||
blockIxs =
|
||||
concatMap (\(row1:row2:row3:_) ->
|
||||
zipWith3 (\blockRow1 blockRow2 blockRow3 ->
|
||||
blockRow1 ++ blockRow2 ++ blockRow3)
|
||||
row1 row2 row3)
|
||||
. chunksOf 3 . map (chunksOf 3) $ rowIxs
|
||||
```
|
||||
|
||||
We start by defining the board state as an enumeration of three value corresponding to the solved,
|
||||
incomplete and invalid states. The `boardState` function takes a board and gives its current state.
|
||||
It does so by checking three conditions:
|
||||
|
||||
1. if any cell in the board does not have only one possible value then the board is incomplete
|
||||
2. if any unit of the board is invalid then the solution is invalid
|
||||
3. else the board is solved
|
||||
|
||||
To find if an unit is invalid, we take all the cells of the unit and check if they have all the digits
|
||||
in between them as per the rules of Sudoku.
|
||||
|
||||
Units are found just by looking up the indexes from the board for each unit. Unit indexes are all the
|
||||
row, column and block indexes taken together. Row and column indexes can be obtained from the simple
|
||||
mathematical formulas. Block indexes are a little trickier to get. It involves taking the row indexes,
|
||||
splitting each row into chunks of three columns, then taking three rows at a time and mapping and
|
||||
concatenating them with a function which zips three rows at a time creating the block indexes.
|
||||
|
||||
A run in _ghci_ shows the indexes to be correct:
|
||||
|
||||
<pre>
|
||||
*Sudoku> mapM_ print rowIxs
|
||||
[0,1,2,3,4,5,6,7,8]
|
||||
[9,10,11,12,13,14,15,16,17]
|
||||
[18,19,20,21,22,23,24,25,26]
|
||||
[27,28,29,30,31,32,33,34,35]
|
||||
[36,37,38,39,40,41,42,43,44]
|
||||
[45,46,47,48,49,50,51,52,53]
|
||||
[54,55,56,57,58,59,60,61,62]
|
||||
[63,64,65,66,67,68,69,70,71]
|
||||
[72,73,74,75,76,77,78,79,80]
|
||||
|
||||
*Sudoku> mapM_ print columnIxs
|
||||
[0,9,18,27,36,45,54,63,72]
|
||||
[1,10,19,28,37,46,55,64,73]
|
||||
[2,11,20,29,38,47,56,65,74]
|
||||
[3,12,21,30,39,48,57,66,75]
|
||||
[4,13,22,31,40,49,58,67,76]
|
||||
[5,14,23,32,41,50,59,68,77]
|
||||
[6,15,24,33,42,51,60,69,78]
|
||||
[7,16,25,34,43,52,61,70,79]
|
||||
[8,17,26,35,44,53,62,71,80]
|
||||
|
||||
*Sudoku> mapM_ print blockIxs
|
||||
[0,1,2,9,10,11,18,19,20]
|
||||
[3,4,5,12,13,14,21,22,23]
|
||||
[6,7,8,15,16,17,24,25,26]
|
||||
[27,28,29,36,37,38,45,46,47]
|
||||
[30,31,32,39,40,41,48,49,50]
|
||||
[33,34,35,42,43,44,51,52,53]
|
||||
[54,55,56,63,64,65,72,73,74]
|
||||
[57,58,59,66,67,68,75,76,77]
|
||||
[60,61,62,69,70,71,78,79,80]
|
||||
</pre>
|
||||
|
||||
See how the row indexes follow the grid indexes as we have taken our grid indexes to be rows first,
|
||||
left to right. If we take row indexes column-wise we get the column indexes. If we take the row
|
||||
indexes block-wise we get the block indexes.
|
||||
|
||||
Let's do a few sample runs of `boardState` in _ghci_:
|
||||
|
||||
<pre>
|
||||
*Sudoku> let (Just board) = readBoard "483921657967345821251876493548132976729564138136798245372689514814253769695417382"
|
||||
*Sudoku> putStr (asciiShowBoard board)
|
||||
+-------+-------+-------+
|
||||
| 4 8 3 | 9 2 1 | 6 5 7 |
|
||||
| 9 6 7 | 3 4 5 | 8 2 1 |
|
||||
| 2 5 1 | 8 7 6 | 4 9 3 |
|
||||
+-------+-------+-------+
|
||||
| 5 4 8 | 1 3 2 | 9 7 6 |
|
||||
| 7 2 9 | 5 6 4 | 1 3 8 |
|
||||
| 1 3 6 | 7 9 8 | 2 4 5 |
|
||||
+-------+-------+-------+
|
||||
| 3 7 2 | 6 8 9 | 5 1 4 |
|
||||
| 8 1 4 | 2 5 3 | 7 6 9 |
|
||||
| 6 9 5 | 4 1 7 | 3 8 2 |
|
||||
+-------+-------+-------+
|
||||
*Sudoku> boardState board
|
||||
SOLVED
|
||||
*Sudoku> let (Just board) = readBoard "48392165796734582125187649354813297672956413.136798245372689514814253769695417382"
|
||||
*Sudoku> boardState board
|
||||
INCOMPLETE
|
||||
*Sudoku> let (Just board) = readBoard "183921657967345821251876493548132976729564138136798245372689514814253769695417382"
|
||||
*Sudoku> boardState board
|
||||
INVALID
|
||||
</pre>
|
||||
|
||||
That seems to be working. Now let's move on to actually solving the Sudoku!
|
||||
|
||||
Backtracking search
|
||||
-------------------
|
||||
|
||||
Constraint propagation
|
||||
----------------------
|
||||
|
||||
The finale
|
||||
----------
|
||||
|
||||
What's next
|
||||
-----------
|
||||
|
||||
Get the code
|
||||
------------
|
||||
|
||||
This post can be downloaded as a compilable Literate Haskell file [here][2]. The Haskell code in the
|
||||
post can be downloaded [here][3] or can be forked [here][4].
|
||||
|
||||
[1]: http://hackage.haskell.org/package/split
|
||||
[2]: /downloads/code/sudoku1.lhs
|
||||
[3]: /downloads/code/sudoku1.hs
|
||||
[4]: https://github.com/abhin4v/abhin4v.github.com/blob/source/source/downloads/code/sudoku1.hs
|
||||
|
|
|
@ -7,6 +7,7 @@ import qualified Data.Map as M
|
|||
import Data.Char (digitToInt, intToDigit)
|
||||
import Data.List (foldl', intersperse, intercalate)
|
||||
import Data.List.Split (chunksOf)
|
||||
import Data.Map ((!))
|
||||
import Control.Monad (foldM, guard)
|
||||
data Digit = ONE | TWO | TRE | FOR | FIV | SIX | SVN | EGT | NIN
|
||||
deriving (Eq, Ord, Enum)
|
||||
|
@ -14,6 +15,8 @@ data Digit = ONE | TWO | TRE | FOR | FIV | SIX | SVN | EGT | NIN
|
|||
instance Show Digit where
|
||||
show digit = show $ fromEnum digit + 1
|
||||
|
||||
allDigits = S.fromList [ONE .. NIN]
|
||||
|
||||
data Cell = Cell { cellIdx :: Int, cellVals :: S.Set Digit }
|
||||
deriving (Eq)
|
||||
|
||||
|
@ -23,7 +26,7 @@ instance Show Cell where
|
|||
type Board = M.Map Int Cell
|
||||
|
||||
emptyBoard :: Board
|
||||
emptyBoard = foldl' (\m i -> M.insert i (Cell i $ S.fromList [ONE .. NIN]) m)
|
||||
emptyBoard = foldl' (\m i -> M.insert i (Cell i allDigits) m)
|
||||
M.empty [0 .. 80]
|
||||
readBoard :: String -> Maybe Board
|
||||
readBoard str = do
|
||||
|
@ -31,7 +34,7 @@ readBoard str = do
|
|||
foldM (\board (i, chr) -> do
|
||||
guard $ chr == '.' || (chr `S.member` S.fromList ['1' .. '9'])
|
||||
let cellVals = if chr == '.'
|
||||
then S.fromList [ONE .. NIN]
|
||||
then allDigits
|
||||
else S.singleton $ toEnum $ digitToInt chr - 1
|
||||
return $ M.insert i (Cell i cellVals) board)
|
||||
emptyBoard
|
||||
|
@ -53,3 +56,26 @@ asciiShowBoard =
|
|||
. chunksOf 9
|
||||
. showBoard
|
||||
where border = "+-------+-------+-------+"
|
||||
data BoardState = SOLVED | INCOMPLETE | INVALID
|
||||
deriving (Eq, Show)
|
||||
|
||||
boardState :: Board -> BoardState
|
||||
boardState board
|
||||
| any (\Cell{..} -> S.size cellVals /= 1) $ M.elems board = INCOMPLETE
|
||||
| any isUnitInvalid units = INVALID
|
||||
| otherwise = SOLVED
|
||||
where
|
||||
isUnitInvalid unitCells =
|
||||
(S.fromList . map (head . S.toList . cellVals) $ unitCells) /= allDigits
|
||||
|
||||
units = map (map (board !)) unitIxs
|
||||
|
||||
unitIxs = rowIxs ++ columnIxs ++ blockIxs
|
||||
rowIxs = map (\i -> [i * 9 .. i * 9 + 8]) [0..8]
|
||||
columnIxs = map (\i -> take 9 [i, i + 9 ..]) [0..8]
|
||||
blockIxs =
|
||||
concatMap (\(row1:row2:row3:_) ->
|
||||
zipWith3 (\blockRow1 blockRow2 blockRow3 ->
|
||||
blockRow1 ++ blockRow2 ++ blockRow3)
|
||||
row1 row2 row3)
|
||||
. chunksOf 3 . map (chunksOf 3) $ rowIxs
|
||||
|
|
|
@ -4,7 +4,7 @@ layout: post
|
|||
title: "Sudoku and Haskell: A Sudoku Solver"
|
||||
date: 2013-03-09 23:35
|
||||
comments: true
|
||||
categories: programming haskell sudoku puzzle
|
||||
categories: programming haskell sudoku
|
||||
published: false
|
||||
---
|
||||
|
||||
|
@ -14,6 +14,9 @@ sub-grids (called blocks) have every digit from 1 to 9. The puzzle starts with s
|
|||
and the player has to fill the rest to reach the solution. Since each unit (column, row or block) has
|
||||
9 cells and has to be filled with all 9 digits - 1 to 9 - there cannot be any duplicates in a unit.
|
||||
|
||||
{% img https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png 200 A typical Sudoku puzzle %}
|
||||
{% img https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Sudoku-by-L2G-20050714_solution.svg/250px-Sudoku-by-L2G-20050714_solution.svg.png 200 The same puzzle with solution numbers marked in red %}
|
||||
|
||||
In this post we look at how to solve a Sudoku with Haskell.
|
||||
|
||||
<!-- more -->
|
||||
|
@ -29,10 +32,11 @@ The code in this post has dependencies on the [`split`][1] package from Hackage.
|
|||
> import Data.Char (digitToInt, intToDigit)
|
||||
> import Data.List (foldl', intersperse, intercalate)
|
||||
> import Data.List.Split (chunksOf)
|
||||
> import Data.Map ((!))
|
||||
> import Control.Monad (foldM, guard)
|
||||
|
||||
|
||||
Basic Setup
|
||||
Basic setup
|
||||
-----------
|
||||
|
||||
Now that the imports are out of the way let's setup the basic functionalities.
|
||||
|
@ -43,6 +47,8 @@ Now that the imports are out of the way let's setup the basic functionalities.
|
|||
> instance Show Digit where
|
||||
> show digit = show $ fromEnum digit + 1
|
||||
>
|
||||
> allDigits = S.fromList [ONE .. NIN]
|
||||
>
|
||||
> data Cell = Cell { cellIdx :: Int, cellVals :: S.Set Digit }
|
||||
> deriving (Eq)
|
||||
>
|
||||
|
@ -52,7 +58,7 @@ Now that the imports are out of the way let's setup the basic functionalities.
|
|||
> type Board = M.Map Int Cell
|
||||
>
|
||||
> emptyBoard :: Board
|
||||
> emptyBoard = foldl' (\m i -> M.insert i (Cell i $ S.fromList [ONE .. NIN]) m)
|
||||
> emptyBoard = foldl' (\m i -> M.insert i (Cell i allDigits) m)
|
||||
> M.empty [0 .. 80]
|
||||
|
||||
A `Digit` is just one of the nine possible values. It derives `Eq`, `Ord` and `Enum`. We use the
|
||||
|
@ -70,7 +76,7 @@ simple list of `Cell`s.
|
|||
`emptyBoard` creates an empty board, with all the cells, unfilled by folding over all the
|
||||
index list and inserting a cell with all possible digits in the map corresponding to each index.
|
||||
|
||||
Reading and Printing the Sudoku
|
||||
Reading and printing the Sudoku
|
||||
-------------------------------
|
||||
|
||||
Next let's write some functions to read a Sudoku board from a string and to print a board so that we
|
||||
|
@ -88,7 +94,7 @@ right column. An example:
|
|||
> foldM (\board (i, chr) -> do
|
||||
> guard $ chr == '.' || (chr `S.member` S.fromList ['1' .. '9'])
|
||||
> let cellVals = if chr == '.'
|
||||
> then S.fromList [ONE .. NIN]
|
||||
> then allDigits
|
||||
> else S.singleton $ toEnum $ digitToInt chr - 1
|
||||
> return $ M.insert i (Cell i cellVals) board)
|
||||
> emptyBoard
|
||||
|
@ -126,14 +132,14 @@ and outputting the digit if the cell is filled else `.`.
|
|||
so by taking the output of `showBoard`, breaking it into chunks corresponding to rows and blocks,
|
||||
inserting spaces and `|` at appropriate places and then joining them with the borders made of `-`.
|
||||
|
||||
Here is an example run in ghci:
|
||||
Here is an example run in _ghci_:
|
||||
|
||||
<pre>
|
||||
*Sudoku> let boardStr = "6..3.2....4.....1..........7.26............543.........8.15........4.2........7.."
|
||||
*Sudoku> let (Just board) = readBoard boardStr
|
||||
*Sudoku> showBoard board
|
||||
"6..3.2....4.....1..........7.26............543.........8.15........4.2........7.."
|
||||
*Sudoku> putStr (prettyShowBoard board)
|
||||
*Sudoku> putStr (asciiShowBoard board)
|
||||
+-------+-------+-------+
|
||||
| 6 . . | 3 . 2 | . . . |
|
||||
| . 4 . | . . . | . 1 . |
|
||||
|
@ -149,9 +155,143 @@ Here is an example run in ghci:
|
|||
+-------+-------+-------+
|
||||
</pre>
|
||||
|
||||
The post can be downloaded as a compilable Literate Haskell file [here][2]. The Haskell code in the
|
||||
post can be downloaded [here][3].
|
||||
Is it solved yet?
|
||||
--------------------
|
||||
|
||||
Before we proceed to write a full-fledged Sudoku solver, it is good to have a function which tells us
|
||||
whether a board is filled completely and whether that solution is a valid one.
|
||||
|
||||
> data BoardState = SOLVED | INCOMPLETE | INVALID
|
||||
> deriving (Eq, Show)
|
||||
>
|
||||
> boardState :: Board -> BoardState
|
||||
> boardState board
|
||||
> | any (\Cell{..} -> S.size cellVals /= 1) $ M.elems board = INCOMPLETE
|
||||
> | any isUnitInvalid units = INVALID
|
||||
> | otherwise = SOLVED
|
||||
> where
|
||||
> isUnitInvalid unitCells =
|
||||
> (S.fromList . map (head . S.toList . cellVals) $ unitCells) /= allDigits
|
||||
>
|
||||
> units = map (map (board !)) unitIxs
|
||||
>
|
||||
> unitIxs = rowIxs ++ columnIxs ++ blockIxs
|
||||
> rowIxs = map (\i -> [i * 9 .. i * 9 + 8]) [0..8]
|
||||
> columnIxs = map (\i -> take 9 [i, i + 9 ..]) [0..8]
|
||||
> blockIxs =
|
||||
> concatMap (\(row1:row2:row3:_) ->
|
||||
> zipWith3 (\blockRow1 blockRow2 blockRow3 ->
|
||||
> blockRow1 ++ blockRow2 ++ blockRow3)
|
||||
> row1 row2 row3)
|
||||
> . chunksOf 3 . map (chunksOf 3) $ rowIxs
|
||||
|
||||
We start by defining the board state as an enumeration of three value corresponding to the solved,
|
||||
incomplete and invalid states. The `boardState` function takes a board and gives its current state.
|
||||
It does so by checking three conditions:
|
||||
|
||||
1. if any cell in the board does not have only one possible value then the board is incomplete
|
||||
2. if any unit of the board is invalid then the solution is invalid
|
||||
3. else the board is solved
|
||||
|
||||
To find if an unit is invalid, we take all the cells of the unit and check if they have all the digits
|
||||
in between them as per the rules of Sudoku.
|
||||
|
||||
Units are found just by looking up the indexes from the board for each unit. Unit indexes are all the
|
||||
row, column and block indexes taken together. Row and column indexes can be obtained from the simple
|
||||
mathematical formulas. Block indexes are a little trickier to get. It involves taking the row indexes,
|
||||
splitting each row into chunks of three columns, then taking three rows at a time and mapping and
|
||||
concatenating them with a function which zips three rows at a time creating the block indexes.
|
||||
|
||||
A run in _ghci_ shows the indexes to be correct:
|
||||
|
||||
<pre>
|
||||
*Sudoku> mapM_ print rowIxs
|
||||
[0,1,2,3,4,5,6,7,8]
|
||||
[9,10,11,12,13,14,15,16,17]
|
||||
[18,19,20,21,22,23,24,25,26]
|
||||
[27,28,29,30,31,32,33,34,35]
|
||||
[36,37,38,39,40,41,42,43,44]
|
||||
[45,46,47,48,49,50,51,52,53]
|
||||
[54,55,56,57,58,59,60,61,62]
|
||||
[63,64,65,66,67,68,69,70,71]
|
||||
[72,73,74,75,76,77,78,79,80]
|
||||
|
||||
*Sudoku> mapM_ print columnIxs
|
||||
[0,9,18,27,36,45,54,63,72]
|
||||
[1,10,19,28,37,46,55,64,73]
|
||||
[2,11,20,29,38,47,56,65,74]
|
||||
[3,12,21,30,39,48,57,66,75]
|
||||
[4,13,22,31,40,49,58,67,76]
|
||||
[5,14,23,32,41,50,59,68,77]
|
||||
[6,15,24,33,42,51,60,69,78]
|
||||
[7,16,25,34,43,52,61,70,79]
|
||||
[8,17,26,35,44,53,62,71,80]
|
||||
|
||||
*Sudoku> mapM_ print blockIxs
|
||||
[0,1,2,9,10,11,18,19,20]
|
||||
[3,4,5,12,13,14,21,22,23]
|
||||
[6,7,8,15,16,17,24,25,26]
|
||||
[27,28,29,36,37,38,45,46,47]
|
||||
[30,31,32,39,40,41,48,49,50]
|
||||
[33,34,35,42,43,44,51,52,53]
|
||||
[54,55,56,63,64,65,72,73,74]
|
||||
[57,58,59,66,67,68,75,76,77]
|
||||
[60,61,62,69,70,71,78,79,80]
|
||||
</pre>
|
||||
|
||||
See how the row indexes follow the grid indexes as we have taken our grid indexes to be rows first,
|
||||
left to right. If we take row indexes column-wise we get the column indexes. If we take the row
|
||||
indexes block-wise we get the block indexes.
|
||||
|
||||
Let's do a few sample runs of `boardState` in _ghci_:
|
||||
|
||||
<pre>
|
||||
*Sudoku> let (Just board) = readBoard "483921657967345821251876493548132976729564138136798245372689514814253769695417382"
|
||||
*Sudoku> putStr (asciiShowBoard board)
|
||||
+-------+-------+-------+
|
||||
| 4 8 3 | 9 2 1 | 6 5 7 |
|
||||
| 9 6 7 | 3 4 5 | 8 2 1 |
|
||||
| 2 5 1 | 8 7 6 | 4 9 3 |
|
||||
+-------+-------+-------+
|
||||
| 5 4 8 | 1 3 2 | 9 7 6 |
|
||||
| 7 2 9 | 5 6 4 | 1 3 8 |
|
||||
| 1 3 6 | 7 9 8 | 2 4 5 |
|
||||
+-------+-------+-------+
|
||||
| 3 7 2 | 6 8 9 | 5 1 4 |
|
||||
| 8 1 4 | 2 5 3 | 7 6 9 |
|
||||
| 6 9 5 | 4 1 7 | 3 8 2 |
|
||||
+-------+-------+-------+
|
||||
*Sudoku> boardState board
|
||||
SOLVED
|
||||
*Sudoku> let (Just board) = readBoard "48392165796734582125187649354813297672956413.136798245372689514814253769695417382"
|
||||
*Sudoku> boardState board
|
||||
INCOMPLETE
|
||||
*Sudoku> let (Just board) = readBoard "183921657967345821251876493548132976729564138136798245372689514814253769695417382"
|
||||
*Sudoku> boardState board
|
||||
INVALID
|
||||
</pre>
|
||||
|
||||
That seems to be working. Now let's move on to actually solving the Sudoku!
|
||||
|
||||
Backtracking search
|
||||
-------------------
|
||||
|
||||
Constraint propagation
|
||||
----------------------
|
||||
|
||||
The finale
|
||||
----------
|
||||
|
||||
What's next
|
||||
-----------
|
||||
|
||||
Get the code
|
||||
------------
|
||||
|
||||
This post can be downloaded as a compilable Literate Haskell file [here][2]. The Haskell code in the
|
||||
post can be downloaded [here][3] or can be forked [here][4].
|
||||
|
||||
[1]: http://hackage.haskell.org/package/split
|
||||
[2]: /downloads/code/sudoku1.lhs
|
||||
[3]: /downloads/code/sudoku1.hs
|
||||
[4]: https://github.com/abhin4v/abhin4v.github.com/blob/source/source/downloads/code/sudoku1.hs
|
||||
|
|
Loading…
Reference in New Issue