sat-simple-0.1.0.0: examples/sat-simple-nonogram.hs
module Main (main) where
import Control.Monad (forM, forM_)
import Control.Monad.IO.Class (liftIO)
import Data.Functor.Compose (Compose (..))
import Data.List (nub)
import Data.Map (Map)
import qualified Data.Map as Map
import Control.Monad.SAT
-------------------------------------------------------------------------------
-- Examples
-------------------------------------------------------------------------------
easyP :: ([[Int]], [[Int]])
easyP = ([[2], [1]], [[2],[1]])
-- | https://en.wikipedia.org/wiki/Nonogram#Example
problemP :: ([[Int]], [[Int]])
problemP = (rows, cols) where
rows =
[ []
, [4]
, [6]
, [2,2]
, [2,2]
, [6]
, [4]
, [2]
, [2]
, [2]
, []
]
cols =
[ []
, [9]
, [9]
, [2,2]
, [2,2]
, [4]
, [4]
, []
]
-- | https://en.wikipedia.org/wiki/Nonogram#/media/File:Nonogram_wiki.svg
problemW :: ([[Int]], [[Int]])
problemW = (rows, cols) where
rows =
[ [8,7,5,7]
, [5,4,3,3]
, [3,3,2,3]
, [4,3,2,2]
, [3,3,2,2]
, [3,4,2,2]
, [4,5,2]
, [3,5,1]
, [4,3,2]
, [3,4,2]
, [4,4,2]
, [3,6,2]
, [3,2,3,1]
, [4,3,4,2]
, [3,2,3,2]
, [6,5]
, [4,5]
, [3,3]
, [3,3]
, [1,1]
]
cols =
[ [1]
, [1]
, [2]
, [4]
, [7]
, [9]
, [2,8]
, [1,8]
, [8]
, [1,9]
, [2,7]
, [3,4]
, [6,4]
, [8,5]
, [1,11]
, [1,7]
, [8]
, [1,4,8]
, [6,8]
, [4,7]
, [2,4]
, [1,4]
, [5]
, [1,4]
, [1,5]
, [7]
, [5]
, [3]
, [1]
, [1]
]
-------------------------------------------------------------------------------
-- Main
-------------------------------------------------------------------------------
main :: IO ()
main = do
nonogram "Easy" easyP
nonogram "Letter P" problemP
nonogram "Letter W" problemW
where
nonogram name p = do
putStrLn name
solP <- uncurry solveNonogram p
putStrLn $ render solP
-------------------------------------------------------------------------------
-- Render
-------------------------------------------------------------------------------
render :: [[Bool]] -> String
render sol = unlines
[ map (\b -> if b then '*' else ' ') l
| l <- sol
]
-------------------------------------------------------------------------------
-- Solve
-------------------------------------------------------------------------------
solveNonogram :: [[Int]] -> [[Int]] -> IO [[Bool]]
solveNonogram rows cols = runSAT $ do
let lits' :: [[SAT s (Lit s)]]
lits' = [ [ newLit | _ <- cols ] | _ <- rows ]
-- create solution variables.
Compose lits <- sequence (Compose lits')
-- row constraints
forM_ (zip rows lits) $ \(r, ls) -> do
regexp r ls
-- column constraints
forM_ (zip cols (transpose lits)) $ \(r, ls) -> do
regexp r ls
numberOfVariables >>= \n -> liftIO $ putStrLn $ "variables: " ++ show n
numberOfClauses >>= \n -> liftIO $ putStrLn $ "clauses: " ++ show n
-- solve
Compose sol <- solve (Compose lits)
return sol
transpose:: [[a]] -> [[a]]
transpose ([]:_) = []
transpose x = map head x : transpose (map tail x)
-------------------------------------------------------------------------------
-- NFA matching
-------------------------------------------------------------------------------
data RE a
= Emp
| Eps
| Chr a
| Rep (RE a)
| App (RE a) (RE a)
-- | Alt (RE a) (RE a)
deriving (Eq, Ord, Show)
{-
alt :: RE a -> RE a -> RE a
alt Emp s = s
alt r Emp = r
alt (Alt r t) s = alt r (alt t s)
alt r s = Alt r s
-}
app :: RE a -> RE a -> RE a
app Emp _ = Emp
app _ Emp = Emp
app Eps s = s
app r Eps = r
app (App r t) s = app r (app t s)
app r s = App r s
nullable :: RE a -> Bool
nullable Emp = False
nullable Eps = True
nullable (Chr _) = False
nullable (Rep _) = True
nullable (App r1 r2) = nullable r1 && nullable r2
-- nullable (Alt r1 r2) = nullable r1 || nullable r2
derivate :: Eq a => a -> RE a -> [RE a]
derivate _ Emp = []
derivate _ Eps = []
derivate c (Chr c') = if c == c' then [Eps] else []
-- derivate c (Alt r s) = derivate c r ++ derivate c s
derivate c (Rep r) = [ app r' (Rep r) | r' <- derivate c r ]
derivate c (App r s)
| nullable r = [ app r' s | r' <- derivate c r ] ++ derivate c s
| otherwise = [ app r' s | r' <- derivate c r ]
derivateAny :: RE a -> [RE a]
derivateAny Emp = []
derivateAny Eps = []
derivateAny (Chr _) = [Eps]
derivateAny (Rep r) = [ app r' (Rep r) | r' <- derivateAny r ]
-- derivateAny (Alt r s) = derivateAny r ++ derivateAny s
derivateAny (App r s)
| nullable r = [ app r' s | r' <- derivateAny r ] ++ derivateAny s
| otherwise = [ app r' s | r' <- derivateAny r ]
-- | Does regexp accept any string of given length.
accepts :: Eq a => Int -> RE a -> Bool
accepts n r
| n <= 0 = nullable r
| otherwise = any (accepts (n - 1)) (nub (derivateAny r))
convert :: [Int] -> RE Bool
convert [] = Rep (Chr False)
convert (n:ns) = App (Rep (Chr False)) $ nOnes n $ convert ns
where
nOnes m r = if m >= 1 then App (Chr True) (nOnes (m - 1) r) else r
regexp :: forall s. [Int] -> [Lit s] -> SAT s ()
regexp r0 ls0 = do
tl <- trueLit
go [(tl, convert r0)] ls0
where
go :: [(Lit s, RE Bool)] -> [Lit s] -> SAT s ()
go s [] = do
-- we should have reached at least one nullable state
assertAtLeastOne
[ l
| (l, r) <- s
, nullable r
]
go s (l:ls) = do
-- next states with a list from which states they can be reached.
let next :: Map (RE Bool) [(Lit s, Bool)]
next = Map.fromListWith (++)
[ (r', [(l', c)])
| (l', r) <- s
, c <- [True, False]
-- nub doesn't seem to affect.
, r' <- nub $ derivate c r
, accepts (length ls) r'
]
-- add definitions for the next NFA states,
-- with their values depending on current `l` and previous states.
s' <- forM (Map.toList next) $ \(r', steps) -> do
n <- addDefinition $ foldr (\/) false
[ lit l' /\ if b then lit l else neg (lit l)
| (l', b) <- steps
]
return (n, r')
go s' ls