Am Mittwoch, 5. April 2006 15:09 schrieb Chris Kuklewicz:
> Henning Thielemann wrote:
> > On Mon, 3 Apr 2006, Jared Updike wrote:
> >> or ambiguously) with your Sudoku solver? A rough mesaure of the
> >> difficulty of the unsolved puzzle could be how long the solver took to
> >> solve it (number of steps) (and the number of possible solutions)? Are
> >> puzzles with multiple solutions usually considered harder or easier?
> >> Are these considered proper puzzles?
> >
> > It's an interesting test to run a Sudoku solver on an empty array. :-)
>
> I am cleaning up my old (aka inexperienced) solver based on Knuth's dancing
> links to put on the wiki.  The code is very different than most Haskell
> solutions, since it revolves around a mutable data structure (which is not
> an MArray).
>
> It "solves" an empty array in 81 steps with no backtracking.   It will
> produce a list of all the solutions of an empty board quite efficiently.
>
> Cleaning up my "logic" based solver will take longer.

I've cleaned up my solver, removed a lot of redundant inference steps and made 
the board a DiffArray (is that really faster?).
Now it completely solves (following all guesses) the 36,628 17-hint puzzles in 
about 32 minutes (1909 secs).
It "solves" an empty board in 81 steps without false guesses, but still takes 
over four seconds (that's the price for excessive inference).

I've also written a version using David F. Place's EnumSet instead of [Int],
that takes less MUT time, but more GC time, so is slower on the 36,628 test, 
but faster for a single puzzle.

If anybody feels like improving the code (especially finding better names for 
the functions) and/or placing it on the wiki, I'll be honoured.

Just out of curiosity, speed was not the objective when I wrote my solver, I 
wanted to avoid guesswork (as much as possible), but in comparison with Cale 
Gibbard's and Alson Kemp's solvers (which are much more beautifully coded), 
it turned out that mine is blazingly fast, so are there faster solvers around 
(in Haskell, in other languages)?

Cheers,
Daniel

-- 

"In My Egotistical Opinion, most people's C programs should be
indented six feet downward and covered with dirt."
        -- Blair P. Houghton
module Sudoku where

import Data.Array.Diff
import Data.List
import Data.Char (isSpace, digitToInt)
import System.IO

type Position = (Int, Int)

pool :: Entry
pool = Poss [1 .. 9]

ixes :: [Int]
ixes = [0 .. 8]

data Entry
    = Def !Int
    | Poss ![Int]
      deriving Eq

instance Show Entry where
    show (Def k)  = show k
    show (Poss s) = case s of
                      []    -> "#"
                      [_]   -> "*"
                      [_,_] -> ":"
                      _     -> " "

type Board = DiffArray Position Entry

emptyBoard :: Board
emptyBoard = listArray ((0,0),(8,8)) $ replicate 81 pool

(?) :: Board -> Position -> Entry
b ? p = b ! toGridPos p

failed :: Board -> Bool
failed = any isImp . elems

solved :: Board -> Bool
solved = all isDef . elems

unique :: Board -> Bool
unique b = case solve b of
             [_] -> True
             _   -> False

----------------------------------------------------------------------
--                         Solving a Puzzle                         --
----------------------------------------------------------------------

place :: Board -> Position -> Int -> Board
place b p@(r,c) k
    = case b!p of
        Def n  -> if k == n then b
                     else b // [(p, Poss [])]
        Poss s -> if k `elem` s then b'
                     else b // [(p, Poss [])]
            where
              b1 = b // [(p,Def k)]
              ri = [(r,i) | i <- ixes, i /= c]
              ci = [(i,c) | i <- ixes, i /= r]
              cn = cellNo p
              good (x,y) = x /= r && y /= c
              bi = filter good [toGridPos (cn,i) | i <- ixes]
              b' = foldl (forbid k) b1 $ ri ++ ci ++ bi

restrict :: Board -> (Position, [Int]) -> Board
restrict b (p,[k]) = place b p k
restrict b (p,s) = case b!p of
                      Poss t -> b // [(p, Poss $ filter (`elem` s) t)]
                      Def k  -> if k `elem` s then b
                                  else b // [(p, Poss [])]

forbid :: Int -> Board -> Position -> Board
forbid k b p
    = case b!p of
        Poss s | k `elem` s -> b // [(p, Poss $ delete k s)]
        _      -> b

certs :: Board -> [(Position, Int)]
certs b = [(p,k) | (p, Poss [k]) <- assocs b]

defCerts :: Board -> (Bool, Board)
defCerts b
    = case certs b of
        []  -> (False, b)
        cs  -> let b1 = foldl (uncurry . place) b cs
                   (_, b2) = defCerts b1
               in (True, b2)

posCell :: Int -> Board -> Int -> Board
posCell cn b k
    = let ps = [(i,mkSt e) | i <- ixes, let e = b ? (cn,i), not (isDef e)]
          fp = findPos k ps
          rs = nub [i `quot` 3 | i <- fp]
          cs = nub [i `rem` 3 | i <- fp]
          rc = filter (/= cn) [(cn `quot` 3) * 3 + i | i <- [0 .. 2]]
          cc = filter (/= cn) [3 * i + (cn `rem` 3) | i <- [0 .. 2]]
          ls = case (rs,cs) of
                ([r], _)  -> [(c',3*r+i) | c' <- rc, i <- [0 .. 2]]
                (_, [c])  -> [(c',3*i+c) | c' <- cc, i <- [0 .. 2]]
                _         -> []
      in case fp of
           [i] -> place b (toGridPos (cn,i)) k
           _   -> foldl (forbid k) b $ map toGridPos ls

treatCell :: Int -> Board -> Board
treatCell cn b
    = foldl (posCell cn) b lst
      where
        lst = unions [mkSt e | i <- ixes, let e = b ? (cn,i),
                      not (isDef e)]

treatCells :: Board -> Board
treatCells b = foldr treatCell b [0 .. 8]

posRow :: Int -> Board -> Int -> Board
posRow r b k
    = let ps = [(i,mkSt e) | i <- ixes, let e = b ! (r,i), not (isDef e)]
          fp = findPos k ps
          cs = nub [i `quot` 3 | i <- fp]
      in case (fp,cs) of
            ([c],_) -> place b (r,c) k
            (_,[c]) -> let ls = [(r',c') | i <- [0 .. 2], let r' = 3*(r `quot` 3) + i, r' /= r, j <- [0 .. 2], let c' = 3*c + j]
                       in foldl (forbid k) b ls
            _       -> b

treatRow :: Int -> Board -> Board
treatRow r b
    = foldl (posRow r) b lst
      where
        lst = unions [mkSt s | i <- ixes, let s = b ! (r,i),
                                    not (isDef s)]

treatRows :: Board -> Board
treatRows b = foldr treatRow b [0 .. 8]

posCol :: Int -> Board -> Int -> Board
posCol c b k
    = let ps = [(i,mkSt e) | i <- ixes, let e = b ! (i,c), not (isDef e)]
          fp = findPos k ps
          rs = nub [i `quot` 3 | i <- fp]
      in case (fp,rs) of
            ([r],_) -> place b (r,c) k
            (_,[r]) -> let ls = [(r',c') | i <- [0 .. 2], let r' = 3*r + i, j <- [0 .. 2], let c' = 3*(c `quot` 3) + j, c' /= c]
                       in foldl (forbid k) b ls
            _       -> b

treatCol :: Int -> Board -> Board
treatCol c b
    =  foldl (posCol c) b lst
      where
        lst = unions [mkSt s | i <- ixes, let s = b ! (i,c),
                                    not (isDef s)]

treatCols :: Board -> Board
treatCols b =  foldr treatCol b [0 .. 8]

infer :: Board -> Board
infer b
    = let b1 = treatCells b
          c1 = certs b1
          b2 = treatRows b1
          c2 = certs b2
          b3 = treatCols b2
          c3 = certs b3
          b4 = treatGroups b3
          c4 = certs b4
      in case (c1,c2,c3,c4) of
           ((_:_),_,_,_) -> b1
           (_,(_:_),_,_) -> b2
           (_,_,(_:_),_) -> b3
           (_,_,_,(_:_)) -> b4
           _             -> if b4 `equal` b then b4 else infer b4

guess :: Board -> [Board]
guess b
    = let ass = filter (not . isDef . snd) $ assocs b
          cmp (_,Poss s1) (_,Poss s2) = compare (length s1) (length s2)
          sas = sortBy cmp ass
      in case sas of
            [] -> []
            ((p,Poss s):_) -> filter (not . failed) [place b p n | n <- s]

solve :: Board -> [Board]
solve b
    | solved b  = [b]
    | failed b  = []
    | otherwise = let b1 = infer b in
                  case defCerts b1 of
                    (True,b2) -> solve b2
                    (_   ,b2) -> if b `equal` b2 then
                                    do bd <- guess b
                                       solve bd
                                    else solve b2

----------------------------------------------------------------------
--                        Inference on Groups                       --
----------------------------------------------------------------------

inferGroup :: Board -> [Position] -> [[(Position, [Int])]]
inferGroup b ps
    = let ass = [(p,b!p) | p <- ps]
          sts = [(p,s) | (p, Poss s) <- ass]
          len = length sts
      in do k <- [2 .. len - 2]
            (xs,ys) <- select k sts
            let ws = unions $ map snd xs
            case compare (length ws) k of
              GT -> fail "nothing found"
              _  -> return $ map (`without` ws) ys

infGr :: Board -> [Position] -> Board
infGr b ps
    = let ups = concat $ inferGroup b ps
      in foldl restrict b ups

indGroups :: [[Position]]
indGroups = [map toGridPos [(cn,i) | i <- ixes] | cn <- ixes]
            ++ [[(r,c) | c <- ixes] | r <- ixes]
            ++ [[(r,c) | r <- ixes] | c <- ixes]

treatGroups :: Board -> Board
treatGroups b = foldl infGr b indGroups

----------------------------------------------------------------------
--                  Reading and Displaying a Board                  --
----------------------------------------------------------------------

readGrid :: String -> Board
readGrid str
    = let nlns = zip [0 .. 8] $ lines str
          tr s = zip [0 .. 8] $ map read . words $ s
          grd  = map (\(n,l) -> map (\(k,m) -> ((n,k),m)) $ tr l) nlns
          plcs = filter ((/= 0) . snd) $ concat grd
      in foldl (uncurry . place) emptyBoard plcs

readLine :: String -> Board
readLine str
    = let nlns = zip [0 .. 8] $ takeBy 9 str
          tr s = zip [0 .. 8] $ map digitToInt s
          grd  = map (\(n,l) -> map (\(k,m) -> ((n,k),m)) $ tr l) nlns
          plcs = filter ((/= 0) . snd) $ concat grd
      in foldl (uncurry . place) emptyBoard plcs

readBoard :: String -> Board
readBoard = readLine . take 81 . filter (not . isSpace)

boardToLists :: Board -> [[Entry]]
boardToLists b = [[b!(r,c) | c <- ixes] | r <- ixes]

-- this is taken from David F. Place's sudoku solver,
-- it's prettier than my original
display :: [[Entry]] -> String
display s = stretch . (intersperse ["---+---+---\n"]) . (stitch knot) . gather $ s
    where
      sewRow = stretch . (intersperse ["|"]) . (stitch show) . gather
      knot r = (sewRow r)++['\n']
      stretch = concat . concat
      stitch f = map (map f)
      gather = takeBy 3

showBoard :: Board -> String
showBoard = display . boardToLists

pretty :: Board -> IO ()
pretty = putStr . showBoard

----------------------------------------------------------------------
--                         Helper Functions                         --
----------------------------------------------------------------------

cellNo :: Position -> Int
cellNo (r,c) = (r `quot` 3) * 3 + c `quot` 3

cellInd :: Position -> Int
cellInd (r,c) =(r `rem` 3) * 3 + c `rem` 3

toCellPos :: Position -> Position
toCellPos (r,c) = (cn,ci)
                  where
                    cn = cellNo (r,c)
                    ci = (r `rem` 3) * 3 + c `rem` 3

toGridPos :: Position -> Position
toGridPos (cn,ci)
    = (r,c)
      where
        r = (cn `quot` 3) * 3 + ci `quot` 3
        c = (cn `rem` 3) * 3 + ci `rem` 3

-- also from David F. Place
takeBy :: Int -> [a] -> [[a]]
takeBy n xs = unfoldr f xs
    where
      f [] = Nothing
      f xs = Just $ splitAt n xs

isDef :: Entry -> Bool
isDef e = case e of
            Def _ -> True
            _     -> False

isCert :: Entry -> Bool
isCert e = case e of
            Poss [_] -> True
            _        -> False

isImp :: Entry -> Bool
isImp e = case e of
            Poss [] -> True
            _       -> False

findPos :: Int -> [(Int, [Int])] -> [Int]
findPos k ps = [y | (y,s) <- ps, k `elem` s]

mkSt :: Entry -> [Int]
mkSt e = case e of
            Poss s -> s
            Def k  -> [k]

equal :: Board -> Board -> Bool
b1 `equal` b2 = elems b1 == elems b2

without :: (a, [Int]) -> [Int] -> (a, [Int])
(x,st) `without` ts = (x, st \\ ts)

-------------------------------------------------------

select :: Int -> [a] -> [([a],[a])]
select 0 xs = [([],xs)]
select _ [] = []
select n (x:xs) = [(x:ys,zs) | (ys,zs) <- select (n-1) xs]
               ++ [(ys,x:zs) | (ys,zs) <- select n xs]

unions :: [[Int]] -> [Int]
unions = foldl merge []

merge :: [Int] -> [Int] -> [Int]
merge [] ys = ys
merge xs [] = xs
merge xss@(x:xs) yss@(y:ys)
    = case compare x y of
        LT -> x:merge xs yss
        EQ -> x:merge xs ys
        GT -> y:merge xss ys
-----------------------------------------------------------
module Main where

import Sudoku
unique :: Board -> Bool
unique b = case solve b of
             [_] -> True
             _   -> False

main :: IO ()
main = do txt <- readFile "sudoku17"
          let lns = lines txt
          print $ all unique $ map readBoard lns
_______________________________________________
Haskell-Cafe mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to