toysolver-0.6.0: samples/programs/nonogram/nonogram.hs
{-# LANGUAGE ScopedTypeVariables, FlexibleContexts, CPP #-}
{-# OPTIONS_GHC -Wall #-}
module Main where
import Control.Monad
import Data.Array.IArray
import Data.Array.Unboxed
import Data.IORef
import Data.List (group)
import qualified Data.Map as Map
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO
import qualified ToySolver.SAT as SAT
import qualified ToySolver.SAT.Encoder.Tseitin as Tseitin
import ToySolver.Internal.Util (setEncodingChar8)
-- row patterns and column patterns
type Problem = ([[Int]], [[Int]])
type Solution = UArray (Int,Int) Bool
readCWDFile :: FilePath -> IO Problem
readCWDFile fname = withFile fname ReadMode hReadCWD
hReadCWD :: Handle -> IO Problem
hReadCWD h = do
nrows <- read <$> hGetLine h
ncols <- read <$> hGetLine h
(rows::[[Int]]) <- replicateM nrows $ liftM (filter (/=0) . map read . words) $ hGetLine h
_ <- hGetLine h -- empty line
(cols::[[Int]]) <- replicateM ncols $ liftM (filter (/=0) . map read . words) $ hGetLine h
unless (length rows == nrows) $ error "row number mismatch"
unless (length cols == ncols) $ error "column number mismatch"
return (rows, cols)
checkSolution :: Problem -> Solution -> IO ()
checkSolution (rows, cols) sol = do
let nrows = length rows
ncols = length cols
forM_ [0..nrows-1] $ \i -> do
let row_i_expected = rows !! i
row_i_actual = [length g | g <- group [sol ! (i,j) | j <- [0..ncols-1]], head g]
unless (row_i_expected == row_i_actual) $ error (show row_i_expected ++ " /= " ++ show row_i_actual)
forM_ [0..ncols-1] $ \j -> do
let col_j_expected = cols !! j
col_j_actual = [length g | g <- group [sol ! (i,j) | i <- [0..nrows-1]], head g]
unless (col_j_expected == col_j_actual) $ error (show col_j_expected ++ " /= " ++ show col_j_actual)
hPrintSolution :: Handle -> Solution -> Char -> Char -> IO ()
hPrintSolution h sol cell0 cell1 = do
let ((r0,c0),(rn,cn)) = bounds sol
forM_ [r0..rn] $ \i -> do
hPutStrLn h [if sol ! (i,j) then cell1 else cell0 | j <- [c0..cn]]
solve :: Problem -> IO (IO (Maybe Solution))
solve (rows, cols) = do
let nrows = length rows
ncols = length cols
solver <- SAT.newSolver
enc <- Tseitin.newEncoder solver
bTrue <- Tseitin.encodeConj enc []
bFalse <- Tseitin.encodeDisj enc []
(bs :: UArray (Int,Int) SAT.Lit) <- liftM (array ((0,0),(nrows-1,ncols-1)) . concat) $ forM [0..nrows-1] $ \i -> do
forM [0..ncols-1] $ \j -> do
b <- SAT.newVar solver
return ((i,j),b)
forM_ (zip [0..] rows) $ \(i, xs) -> do
ref <- newIORef Map.empty
let f j []
| j >= ncols = return bTrue
| otherwise = do
m <- readIORef ref
case Map.lookup (j,[]) m of
Just b -> return b
Nothing -> do
b' <- f (j+1) []
b <- Tseitin.encodeConj enc [- (bs ! (i,j)), b']
writeIORef ref (Map.insert (j,[]) b m)
return b
f j ns@(_ : _) | j + sum ns + length ns - 1 > ncols = return bFalse
f j (n : ns) = do
m <- readIORef ref
case Map.lookup (j, n:ns) m of
Just b -> return b
Nothing -> do
b1 <- do
b1' <- f (j+1) (n : ns)
Tseitin.encodeConj enc [- (bs ! (i,j)), b1']
b2 <- do
b2' <- f (j+n+1) ns
Tseitin.encodeConj enc $ [bs ! (i,j') | j' <- [j..j+n-1]] ++ [- (bs ! (i,j+n)) | j+n < ncols] ++ [b2']
b <- Tseitin.encodeDisj enc [b1,b2]
writeIORef ref (Map.insert (j,n:ns) b m)
return b
b <- f 0 xs
SAT.addClause solver [b]
forM_ (zip [0..] cols) $ \(j, xs) -> do
ref <- newIORef Map.empty
let f i []
| i >= nrows = return bTrue
| otherwise = do
m <- readIORef ref
case Map.lookup (i,[]) m of
Just b -> return b
Nothing -> do
b' <- f (i+1) []
b <- Tseitin.encodeConj enc [- (bs ! (i,j)), b']
writeIORef ref (Map.insert (i,[]) b m)
return b
f i ns@(_ : _) | i + sum ns + length ns - 1 > nrows = return bFalse
f i (n : ns) = do
m <- readIORef ref
case Map.lookup (i, n:ns) m of
Just b -> return b
Nothing -> do
b1 <- do
b1' <- f (i+1) (n : ns)
Tseitin.encodeConj enc [- (bs ! (i,j)), b1']
b2 <- do
b2' <- f (i+n+1) ns
Tseitin.encodeConj enc $ [bs ! (i',j) | i' <- [i..i+n-1]] ++ [- (bs ! (i+n,j)) | i+n < nrows] ++ [b2']
b <- Tseitin.encodeDisj enc [b1,b2]
writeIORef ref (Map.insert (i,n:ns) b m)
return b
b <- f 0 xs
SAT.addClause solver [b]
return $ do
ret <- SAT.solve solver
if not ret then
return Nothing
else do
m <- SAT.getModel solver
SAT.addClause solver [if val then -var else var | (var,val) <- assocs m]
let sol = amap (SAT.evalLit m) bs
return (Just sol)
data Options
= Options
{ optHelp :: Bool
, optSolLim :: Int
}
defaultOptions :: Options
defaultOptions =
Options
{ optHelp = False
, optSolLim = 1
}
options :: [OptDescr (Options -> Options)]
options =
[ Option ['h'] ["help"] (NoArg (\opt -> opt{ optHelp = True })) "show help"
, Option ['n'] []
(ReqArg (\val opt -> opt{ optSolLim = read val }) "<int>")
"maximum number of solutions to enumerate, or -1 to enumerate all solutions (default: 1)"
]
showHelp :: Handle -> IO ()
showHelp h = hPutStrLn h (usageInfo header options)
where
header = "Usage: nonogram [OPTIONS] FILE"
main :: IO ()
main = do
#ifdef FORCE_CHAR8
setEncodingChar8
#endif
args <- getArgs
case getOpt Permute options args of
(_,_,errs@(_:_)) -> do
mapM_ putStrLn errs
exitFailure
(o,args2,[]) -> do
let opt = foldl (flip id) defaultOptions o
when (optHelp opt) $ do
showHelp stdout
exitSuccess
case args2 of
[] -> do
showHelp stderr
exitFailure
fname : _ -> do
prob <- readCWDFile fname
act <- solve prob
let loop n | optSolLim opt >= 0, n >= optSolLim opt = do
hPutStrLn stderr $ "reached to solution enumeration limit " ++ show n
loop n = do
m <- act
case m of
Nothing -> do
hPutStrLn stderr $ "enumerated all of " ++ show n ++ " solutions"
Just sol -> do
checkSolution prob sol
when (n > 0) $ hPutStrLn stdout ""
hPrintSolution stdout sol '.' '#'
hFlush stdout
loop (n+1)
loop (0::Int)