[Haskell-cafe] Norvig's Sudoku Solver in Haskell
Daniel Fischer
daniel.is.fischer at web.de
Mon Aug 27 15:52:09 EDT 2007
Am Montag, 27. August 2007 10:09 schrieb manu:
> Daniel Fischer's modifications to my original program lead to a 400 %
> speed boost !!!
> (It now runs in 22 seconds on my machine)
> He avoided unecessary calls to 'length', uses Array instead of Map,
> refactored 'search' function (details below)
>
Ouch! I should've looked at the code more closely.
That had a bug which resulted in LOTS of futile work.
Fixed that and the Array version now runs in 3 seconds on my computer
(previous version took 60), the corresponding Map version runs in 7.
What was the saying, 'The best optimisation is a better algorithm'?
Code below.
Cheers,
Daniel
{-
This is an attempt to implement in Haskell, Peter Norvig's sudoku
solver :
"Solving Every Sudoku Puzzle" (http://norvig.com/sudoku.html)
In Norvig's program, methods which change a grid return either a new
grid, either False (failure).
Here I use Maybe, and return Just grid or Nothing in case of failure
-}
module Main where
import Data.List hiding (lookup)
import Data.Array
import Control.Monad
import Data.Maybe
--------------------------------------------------
-- Types
type Digit = Char
type Square = (Char,Char)
type Unit = [Square]
-- We represent our grid as an array
type Grid = Array Square [Digit]
--------------------------------------------------
-- Setting Up the Problem
rows = "ABCDEFGHI"
cols = "123456789"
digits = "123456789"
box = (('A','1'),('I','9'))
cross :: String -> String -> [Square]
cross rows cols = [ (r,c) | r <- rows, c <- cols ]
squares :: [Square]
squares = cross rows cols -- [('A','1'),('A','2'),('A','3'),...]
peers :: Array Square [Square]
peers = array box [(s, set (units!s)) | s <- squares ]
where
set = nub . concat
unitlist :: [Unit]
unitlist = [ cross rows [c] | c <- cols ] ++
[ cross [r] cols | r <- rows ] ++
[ cross rs cs | rs <- ["ABC","DEF","GHI"], cs <-
["123","456","789"]]
-- this could still be done more efficiently, but what the heck...
units :: Array Square [Unit]
units = array box [(s, [filter (/= s) u | u <- unitlist, elem s u ]) | s <-
squares]
allPossibilities :: Grid
allPossibilities = array box [ (s,digits) | s <- squares ]
--------------------------------------------------
-- Parsing a grid into a Map
parsegrid :: String -> Maybe Grid
parsegrid g = do regularGrid g
foldM assign allPossibilities (zip squares g)
where regularGrid :: String -> Maybe String
regularGrid g = if all (\c -> (elem c "0.-123456789")) g
then (Just g)
else Nothing
--------------------------------------------------
-- Propagating Constraints
assign :: Grid -> (Square, Digit) -> Maybe Grid
assign g (s,d) = if (elem d digits)
then do -- check that we are assigning a digit and not a '.'
let ds = g!s
toDump = delete d ds
foldM eliminate g (zip (repeat s) toDump)
else return g
eliminate :: Grid -> (Square, Digit) -> Maybe Grid
eliminate g (s,d) = let cell = g!s in
if not (elem d cell) then return g -- already eliminated
-- else d is deleted from s' values
else do let newCell = delete d cell
newV = g // [(s,newCell)]
newV2 <- case newCell of
-- contradiction : Nothing
terminates the computation
[] -> Nothing
-- if there is only one value (d')
left in square, remove it from peers
[d'] -> do let peersOfS = peers!s
foldM eliminate newV
(zip peersOfS (repeat d'))
-- else : return the new grid
_ -> return newV
-- Now check the places where d appears in the
units of s
foldM (locate d) newV2 (units ! s)
locate :: Digit -> Grid -> Unit -> Maybe Grid
locate d g u = case filter (elem d . (g !)) u of
[] -> Nothing
[s] -> assign g (s,d)
_ -> return g
--------------------------------------------------
-- Search
search :: Grid -> Maybe Grid
search g = case [(l,(s,xs)) | (s,xs) <- assocs g, let l = length xs, l /= 1]
of
[] -> return g
ls -> do let (_,(s,ds)) = minimum ls
msum [assign g (s,d) >>= search | d <- ds]
solve :: String -> Maybe Grid
solve str = do
grd <- parsegrid str
search grd
--------------------------------------------------
-- Display solved grid
printGrid :: Grid -> IO ()
printGrid = putStrLn . gridToString
gridToString :: Grid -> String
gridToString g = let l0 = elems g
l1 = (map (\s -> " " ++ s ++ " ")) l0 -- ["1 "," 2
",...]
l2 = (map concat . sublist 3) l1 -- ["1 2 3 ","
4 5 6 ",...]
l3 = (sublist 3) l2 -- [["1 2 3
"," 4 5 6 "," 7 8 9 "],...]
l4 = (map (concat . intersperse "|")) l3 -- ["1 2 3 | 4
5 6 | 7 8 9 ",...]
l5 = (concat . intersperse [line] . sublist 3) l4
in unlines l5
where sublist n [] = []
sublist n xs = take n xs : sublist n (drop n xs)
line = hyphens ++ "+" ++ hyphens ++ "+" ++ hyphens
hyphens = take 9 (repeat '-')
--------------------------------------------------
main :: IO ()
main = do
grids <- fmap lines $ readFile "top95.txt"
mapM_ printGrid $ mapMaybe solve grids
More information about the Haskell-Cafe
mailing list