Liyang/sudoku.hs
Jump to navigation
Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
It's slow. That makes me sad.
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."
--}}}
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)
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