Liyang/sudoku.hs
From HaskellWiki
It's slow. That makes me sad.
1 sudoku.hs
{-# OPTIONS -cpp -fglasgow-exts -fno-monomorphism-restriction #-} {- -} #define EXHAUSTIVE 0 #define FORK_WORKERS 1 #define FORK_GUESSES 2 #define FORK_OS 4 #define FLAG(flag) (1 << flag) #define OPTION(flag) (OPTIONS & FLAG(flag)) #ifndef OPTIONS # define OPTIONS ( FLAG(FORK_WORKERS) ) {- |FLAG(FORK_GUESSES) |FLAG(EXHAUSTIVE) -} #endif module Main where import Prelude hiding ( all, any, concat, elem, foldl, pi ) import Control.Arrow ( (***), (&&&) ) import Control.Applicative import Control.Monad import Control.Concurrent import Control.Concurrent.STM import Control.Exception import Data.Bits import Data.Char import Data.List ( intersperse, unfoldr ) import Data.Maybe import Data.Monoid import Data.Foldable import Data.Traversable import Data.Ix --{{{ Tensor/Cartesian product on collections. tensor :: (Applicative f, Applicative g) => (alpha -> beta -> gamma) -> f alpha -> g beta -> f (g gamma) tensor f a b = (\ x -> f <$> pure x <*> b) <$> a --}}} --{{{ Indexable collections. class Indexable f i | f -> i where indexModify :: i -> (alpha -> (beta, alpha)) -> f alpha -> (beta, f alpha) -- Sometimes we only want to index. pi :: Indexable f i => i -> f alpha -> alpha pi i = fst . indexModify i (id &&& id) -- Or to just modify. modify :: Indexable f i => i -> (alpha -> alpha) -> (f alpha -> f alpha) modify i f = snd . indexModify i (id &&& f) --}}} --{{{ Three is the magic number: base three ordinals. data Ti = T0 | T1 | T2 deriving (Show, Read, Bounded, Eq, Enum, Ord, Ix) type TiTi = (Ti, Ti) type Coord = (TiTi, TiTi) boundedSize :: (Bounded alpha, Ix alpha) => alpha -> Int boundedSize = rangeSize . (m0 &&& m1) where m0, m1 :: Bounded alpha => alpha -> alpha m0 w = minBound; m1 w = maxBound fromPair :: (Bounded alpha, Ix alpha, Enum alpha) => (beta -> Int) -> (beta, alpha) -> Int fromPair f (y, x) = boundedSize x * f y + fromEnum x toPair :: (Bounded alpha, Ix alpha, Enum alpha) => (Int -> beta) -> Int -> (beta, alpha) toPair f n = result where -- this is sick: result = (f *** toEnum) (n `divMod` boundedSize (snd result)) instance ( Bounded alpha, Ix alpha, Enum alpha , Bounded beta, Ix beta, Enum beta ) => Enum (beta, alpha) where fromEnum = fromPair fromEnum toEnum = toPair toEnum --}}} --{{{ Triple: 3. data Triple alpha = T alpha alpha alpha deriving (Show, Bounded, Eq, Ord, Ix) instance Functor Triple where fmap = fmapDefault instance Foldable Triple where foldMap = foldMapDefault instance Traversable Triple where traverse f (T x y z) = T <$> f x <*> f y <*> f z iTriple = T T0 T1 T2 instance Indexable Triple Ti where indexModify i f (T a b c) = case i of T0 -> (x, T a' b c ) where (x, a') = f a T1 -> (x, T a b' c ) where (x, b') = f b T2 -> (x, T a b c' ) where (x, c') = f c instance Applicative Triple where pure x = T x x x T f g h <*> T x y z = T (f x) (g y) (h z) instance (Bounded alpha, Ix alpha, Enum alpha) => Enum (Triple alpha) where fromEnum (T a b c) = (fromPair . fromPair) fromEnum ((a, b), c) toEnum n = T a b c where ((a, b), c) = (toPair . toPair) toEnum n instance Read alpha => Read (Triple alpha) where readsPrec _ s = [ (T a b c, v) | (a, t) <- reads s, (b, u) <- reads t, (c, v) <- reads u ] showTriple :: String -> (alpha -> String) -> Triple alpha -> String showTriple d s = concat . intersperse d . toList . fmap s --}}} --{{{ Region: 3x3; aka rows, cols, boxes, cell constraints... whatever. newtype Region alpha = Region { unRegion :: Triple (Triple alpha) } deriving Eq instance Functor Region where fmap = fmapDefault instance Foldable Region where foldMap = foldMapDefault instance Traversable Region where traverse f (Region tt) = Region <$> traverse (traverse f) tt iRegion = Region (tensor (,) iTriple iTriple) instance Indexable Region TiTi where indexModify (j, i) f (Region tt) = (id *** Region) $ indexModify j (indexModify i f) tt instance Applicative Region where pure = Region . pure . pure Region ttf <*> Region ttx = Region ((<*>) <$> ttf <*> ttx) instance Read alpha => Read (Region alpha) where readsPrec p s = (Region *** id) <$> readsPrec p s instance Show alpha => Show (Region alpha) where showsPrec _ = (++) . showTriple " " (showTriple " " show) . unRegion --}}} --{{{ Cell: 3x3; different Read/Show, but otherwise identical to Regions. newtype Cell alpha = Cell { unCell :: Region alpha } deriving Eq instance Functor Cell where fmap = fmapDefault instance Foldable Cell where foldMap = foldMapDefault instance Traversable Cell where traverse f (Cell r) = Cell <$> traverse f r iCell = Cell iRegion instance Indexable Cell TiTi where indexModify ji f (Cell r) = (id *** Cell) (indexModify ji f r) instance Applicative Cell where pure = Cell . pure Cell rf <*> Cell rx = Cell (rf <*> rx) instance Read (Cell Bool) where readsPrec _ s = case dropWhile isSpace s of '{' : '-' : c : '-' : '}' : t -> reads (c : t) '{' : a : b : c : '}' : t | all isOctDigit mask -> [(partialCell, t)] where mask = T a b c partialCell = (Cell . Region . fmap (toEnum . digitToInt)) mask c : t | c `elem` ".0_-?" -> [(emptyCell, t)] where emptyCell = (Cell . Region . pure . pure) True c : t | c >= '1' && c <= '9' -> [(fullCell, t)] where fullCell = (Cell . Region . toEnum . (2 ^) . (8 -) . pred . read) [c] _ -> [] instance Show (Cell Bool) where show cell@(Cell (Region tt)) = case solutions cell of [n] -> "{-" ++ show (fromEnum n + 1) ++ "-}" _ -> "{" ++ showTriple "" (show . fromEnum) tt ++ "}" --}}} --{{{ Grid: 3x3 x 3x3 newtype Grid alpha = Grid { unGrid :: Region (Region alpha) } deriving Eq instance Functor Grid where fmap = fmapDefault instance Foldable Grid where foldMap = foldMapDefault instance Traversable Grid where traverse f (Grid rr) = Grid <$> traverse (traverse f) rr iGrid = Grid (tensor (,) iRegion iRegion) instance Indexable Grid Coord where indexModify (lk, ji) f (Grid rr) = (id *** Grid) $ indexModify lk (indexModify ji f) rr instance Applicative Grid where pure = Grid . pure . pure Grid rrf <*> Grid rrx = Grid ((<*>) <$> rrf <*> rrx) instance Read alpha => Read (Grid alpha) where readsPrec p s = (Grid *** id) <$> readsPrec p s instance Show alpha => Show (Grid alpha) where show = showTriple "\n\n" (showTriple "\n" show) . unRegion . unGrid where --}}} --{{{ Cube: 3x3 x 3x3 x 3x3; grids with cell constraints. newtype Cube alpha = Cube { unCube :: Grid (Cell alpha) } deriving Eq instance Functor Cube where fmap = fmapDefault instance Foldable Cube where foldMap = foldMapDefault instance Traversable Cube where traverse f (Cube gc) = Cube <$> traverse (traverse f) gc instance Applicative Cube where pure = Cube . pure . pure Cube gcf <*> Cube gcx = Cube ((<*>) <$> gcf <*> gcx) instance Read (Cube Bool) where readsPrec p s = (Cube *** id) <$> readsPrec p s instance Show (Cube Bool) where show = show . unCube --}}} --{{{ views: returns alternative representations of the grid. type Auto alpha = alpha -> alpha type Rank6 t alpha = t (t (t (t (t (t alpha))))) views :: Cube alpha -> Region (Cube alpha) views cube = (assemble . ($ disassemble cube)) <$> mkViews where mkViews :: (Applicative t, Traversable t) => Region (Auto (Rank6 t alpha)) mkViews = (fmap traversals . Region) $ T -- 0 1 2 3 4 5 -- J I l k n m -- row -- l k J I n m -- col -- l k n m J I -- bit (T [0,1] [2,3] [4,5]) -- J l I k n m -- box -- l k J n I m -- ? -- I l k n J m -- ? (T [0,2] [2,4] [4,0]) -- l J k I n m -- ? -- l k n J m I -- ? -- l I k n m J -- ? (T [1,3] [3,5] [5,1]) disassemble :: Cube alpha -> Rank6 Triple alpha disassemble = unRegion . fmap unRegion . unGrid . fmap (unRegion . unCell) . unCube assemble :: Rank6 Triple alpha -> Cube alpha assemble = Cube . fmap (Cell . Region) . Grid . fmap Region . Region traversals :: (Applicative t, Traversable t) => [Int] -> Auto (Rank6 t alpha) traversals = appEndo . fold . fmap (trs !!) . reverse . reindex where trs :: (Applicative t, Traversable t) => [Endo (Rank6 t alpha)] trs = Endo <$> [tr0, tr1, tr2, tr3, tr4, tr5] where tr0 = id :: (Applicative t, Traversable t) => Auto (t alpha) tr1 = traverse tr0 :: (Applicative t, Traversable t) => Auto (t (t alpha)) tr2 = traverse tr1 :: (Applicative t, Traversable t) => Auto (t (t (t alpha))) tr3 = traverse tr2 :: (Applicative t, Traversable t) => Auto (t (t (t (t alpha)))) tr4 = traverse tr3 :: (Applicative t, Traversable t) => Auto (t (t (t (t (t alpha))))) tr5 = traverse tr4 :: (Applicative t, Traversable t) => Auto (t (t (t (t (t (t alpha)))))) -- Calculate the traversals needed to obtain the required reindexing. -- Not always the most efficient in terms of operations needed... reindex :: [Int] -> [Int] reindex = unfoldr shifts . reverse where shifts [] = Nothing shifts (0:t) = shifts t shifts (1:0:t) = shifts t shifts (h:t) = Just (h, flip fmap t $ \ d -> if h > d then d + 1 else d) -- Just the rows, columns and boxes plzkthx. decompose :: Cube alpha -> Triple (Cube alpha) decompose grid = flip pi vs <$> T (T0, T0) (T0, T1) (T1, T0) where vs = views grid -- Select the relevalt row/col/box, given an (y, x) offset. select :: Triple (Cube alpha) -> Coord -> Triple (Region (Cell alpha)) select rcbs ((l, k), (j, i)) = pi <$> T (l, k) (j, i) (l, j) <*> (unGrid . unCube <$> rcbs) --}}} --{{{ solutions: produce a list of candidates, given a cell. solutions :: Cell Bool -> [TiTi] solutions = map fst . filter snd . (fmap (,) iCell <*>) --}}} --{{{ collapse: locate first non-solved cell with fewest possibilities and collapse it down. collapse :: (Functor f, Foldable f, Enum i, Indexable f i) => f (Bool, Cell Bool) -> Maybe [f (Cell Bool)] collapse cells = makeGrids <$> collapseMin cells where makeGrids (sols, coord) = [ modify coord (const cell) (snd <$> cells) | cell <- sols ] collapseMin :: (Functor f, Foldable f, Enum i, Indexable f i) => f (Bool, Cell Bool) -> Maybe ([Cell Bool], i) collapseMin = uncurry (fmap . const . fmap toEnum) . snd . foldl minCell (0, (undefined, Nothing)) . fmap collapseCell where -- undefined? Madness! But I want Ord (Maybe Int) on length and the -- (fmap . const . fmap . toEnum) ensures the insanity never escapes -- (counter :: Int, ((solutions :: [Cell Bool], index :: Int), length :: Maybe Int)) minCell :: (Int, (([Cell Bool], Int), Maybe Int)) -> Maybe [Cell Bool] -> (Int, (([Cell Bool], Int), Maybe Int)) minCell (i, min@(_, lenThat)) = (,) (succ i) . maybe min choose where choose this | lenThis > lenThat = ((this, i), lenThis) | otherwise = min where -- |Ord alpha => Ord (Maybe alpha)| considers Nothing to be $\bot$, -- rather than $\top$. We get the latter by flipping the ordering -- on |alpha| (and swapping |(<)| with |(>)|), hence the |negate|. lenThis = case length this of 1 -> Nothing n -> Just (negate n) -- zero slips through! collapseCell :: (Bool, Cell Bool) -> Maybe [Cell Bool] collapseCell (True, cell) = Nothing collapseCell (False, cell) = Just (flip pi masks <$> solutions cell) where masks = Cell <$> tensor (==) iRegion iRegion --}}} --{{{ Software Transactional Memory; forking. instance Applicative STM where pure = return (<*>) = ap type TBool = TVar Bool #if FORK_OS fork = forkOS #else fork = forkIO #endif --}}} --{{{ eliminator: mark non-solutions from cells in the same row/col/box. type Eliminator = TiTi -> STM () -- Make an eliminator, given a cell and its associated row/col/box. -- Be careful not to mark the solution itself though. eliminator :: Cell TBool -> Triple (Region (Cell TBool)) -> Eliminator eliminator solution rcb n = elimRCB ((pi n <$>) <$> rcb) where elimRCB :: Triple (Region TBool) -> STM () elimRCB = traverse_ . traverse_ $ \ tflag -> unless (pi n solution == tflag) $ do flag <- readTVar tflag when flag (writeTVar tflag False) --}}} --{{{ worker: check each cell for changes in constraints. worker :: TBool -> Cell TBool -> Eliminator -> STM () worker tsolved tcell elim = do cell <- traverse readTVar tcell case solutions cell of [] -> do -- Bad End. #if OPTION(FORK_WORKERS) return () #else retry #endif [n] -> do elim n; writeTVar tsolved True _ -> retry --}}} --{{{ launchMissiles, waitMissiles: constraint propagation. type State alpha = (Grid alpha, Cube alpha) #if OPTION(FORK_WORKERS) waitMissiles :: State TBool -> STM () waitMissiles (tsolved, Cube tgrid) = do let boo tmarked tcell = do marked <- readTVar tmarked cell <- solutions <$> traverse readTVar tcell case cell of [] -> return False [_] -> do unless marked retry; return True _ -> return True let elseThen _ t True = t elseThen e _ False = e foldrM (elseThen (return False)) True (boo <$> tsolved <*> tgrid :: Grid (STM Bool)) return () #endif launchMissiles :: State Bool -> IO (State Bool) launchMissiles (solved, cube) = do tsolved <- atomically $ traverse newTVar solved tcube@(Cube tgrid) <- atomically $ traverse newTVar cube let tcubes = decompose tcube :: Triple (Cube TBool) let elims = {-# SCC "elims" #-} eliminator <$> tgrid <*> (select tcubes <$> iGrid) :: Grid Eliminator let workers = {-# SCC "workers" #-} worker <$> tsolved <*> tgrid <*> elims :: Grid (STM ()) #if OPTION(FORK_WORKERS) let whip True _ = {-# SCC "whip" #-} return Nothing whip False w = Just <$> fork (atomically w) tids <- {-# SCC "whipping" #-} sequenceA (whip <$> solved <*> workers) -- Wait for all the missiles to hit their target. Or any to malfunction. atomically (waitMissiles (tsolved, tcube)) traverse_ (traverse_ killThread) tids #else tdone <- atomically (newTVar False) let whip rest (tdone, w) = do done <- readTVar tdone if done then rest else w `orElse` rest let loop = do atomically (foldl whip (writeTVar tdone True) ((,) <$> tsolved <*> workers)) done <- atomically (readTVar tdone) unless done loop loop #endif cube' <- atomically (traverse readTVar tcube) solved' <- atomically (traverse readTVar tsolved) return (solved', cube') --}}} --{{{ mad: make guesses. mad :: State Bool -> IO Bool mad boo = do (solved', cube') <- launchMissiles boo case fmap Cube <$> collapse ((,) <$> solved' <*> unCube cube') of Nothing -> do print cube' return True Just next -> case next of [] -> return False -- Bad End. _ -> do #if OPTION(FORK_GUESSES) children <- for next $ \ c -> do sem <- atomically (newTVar Nothing) tid <- fork $ mad (solved', c) >>= atomically . writeTVar sem . Just return (sem, tid) let wait = do done <- atomically (traverse (readTVar . fst) children) # if OPTION(EXHAUSTIVE) if any isNothing done then wait else return (any (fromMaybe False) done) # else if any (fromMaybe False) done then do traverse_ (killThread . snd) children return True else if case any isNothing done then wait else return False # endif wait #else # if OPTION(EXHAUSTIVE) traverse_ (mad . (,) solved') next return True -- don't care # else let cow True _ = return True cow False c = mad (solved', c) foldlM cow False next # endif #endif --}}} --{{{ go, main: solver entry point. go :: Cube Bool -> IO Bool go = mad . (,) (pure False) main :: IO () main = do go . read =<< getContents return () --}}} --{{{ easy, gentle, diabolical, unsolvable, minimal :: Cube Bool easy, gentle, diabolical, unsolvable, minimal :: Cube Bool easy = read "\ 2....1.38\ ........5\ .7...6...\ .......13\ .981..257\ 31....8..\ 9..8...2.\ .5..69784\ 4..25...." gentle = read "\ .1.42...5\ ..2.71.39\ .......4.\ 2.71....6\ ....4....\ 6....74.3\ .7.......\ 12.73.5..\ 3...82.7." diabolical = read "\ .9.7..86.\ .31..5.2.\ 8.6......\ ..7.5...6\ ...3.7...\ 5...1.7..\ ......1.9\ .2.6..35.\ .54..8.7." unsolvable = read "\ 1..9.7..3\ .8.....7.\ ..9...6..\ ..72.94..\ 41.....95\ ..85.43..\ ..3...7..\ .5.....4.\ 2..8.6..9" minimal = read "\ .98......\ ....7....\ ....15...\ 1........\ ...2....9\ ...9.6.82\ .......3.\ 5.1......\ ...4...2." --}}}
2 Makefile
TARGETS := sudoku HSFLAGS := $(DEFINES) -O3 .PHONY: all alternatives all: $(TARGETS) %: %.hs ghc $(HSFLAGS) -threaded -o $@ --make $< %: %.lhs ghc $(HSFLAGS) -threaded -o $@ --make $< profile-%: %.hs ghc $(HSFLAGS) -prof -auto-all -o $@ --make $< profile-%.prof: profile-% ./$< +RTS -p -i0.02 -RTS .PHONY: clean clean: rm -f $(TARGETS:=.o) $(TARGETS:=.hi) $(TARGETS)
3 build-alternatives
#! /bin/bash
NAME=sudoku
for ((i = 0; i < 16; i++)) ; do
EXT=""
[ "$(($i & 1))" = "0" ] || EXT="$EXT-exhaustive"
[ "$(($i & 2))" = "0" ] || EXT="$EXT-fork_workers"
[ "$(($i & 4))" = "0" ] || EXT="$EXT-fork_guesses"
[ "$(($i & 8))" = "0" ] || EXT="$EXT-forkOS"
EXT="${EXT:--boring}"
make clean
make DEFINES="-DOPTIONS=$i" "$NAME"
mv "$NAME" "$NAME$EXT"
done
