Compare commits

...

7 Commits

2 changed files with 171 additions and 86 deletions

View File

@ -14,6 +14,9 @@ description: Please see the README on GitHub at <https://github.com/abhi
dependencies: dependencies:
- base >= 4.7 && < 5 - base >= 4.7 && < 5
- containers - containers
- vector
- parallel
- deepseq
executables: executables:
sudoku: sudoku:
@ -21,6 +24,9 @@ executables:
source-dirs: src source-dirs: src
ghc-options: ghc-options:
- -O2 - -O2
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies: dependencies:
- split - split

View File

@ -1,15 +1,21 @@
{-# LANGUAGE LambdaCase #-}
module Main where module Main where
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Data.Function ((&)) import Data.Function ((&))
import qualified Control.Monad import qualified Control.Monad
import qualified Control.Monad.ST
import qualified Data.Char import qualified Data.Char
import qualified Data.Function import qualified Data.Function
import qualified Data.List.Split
import qualified Data.List import qualified Data.List
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import qualified Data.Word import qualified Data.Word
import qualified Data.Bits import qualified Data.Bits
import Data.Vector.Unboxed ((!))
import qualified Data.Vector.Unboxed.Mutable
import qualified Data.Vector.Unboxed
import qualified Data.Maybe
import Control.Parallel.Strategies (withStrategy, rdeepseq, parBuffer)
fixM :: (Eq t, Monad m) => (t -> m t) -> t -> m t fixM :: (Eq t, Monad m) => (t -> m t) -> t -> m t
fixM f x = f x >>= \x' -> if x' == x then return x else fixM f x' fixM f x = f x >>= \x' -> if x' == x then return x else fixM f x'
@ -17,56 +23,102 @@ fixM f x = f x >>= \x' -> if x' == x then return x else fixM f x'
setBits :: Data.Word.Word16 -> [Data.Word.Word16] -> Data.Word.Word16 setBits :: Data.Word.Word16 -> [Data.Word.Word16] -> Data.Word.Word16
setBits = Data.List.foldl' (Data.Bits..|.) setBits = Data.List.foldl' (Data.Bits..|.)
data Cell = Fixed Data.Word.Word16 type Cell = Data.Word.Word16
| Possible Data.Word.Word16 type Grid = Data.Vector.Unboxed.Vector Cell
deriving (Show, Eq) type MGrid s = Data.Vector.Unboxed.Mutable.STVector s Cell
type CellIxs = [Int]
type Row = [Cell] isPossible, isFixed :: Cell -> Bool
type Grid = [Row] isPossible = flip Data.Bits.testBit 15
isFixed = not . isPossible
isPossible :: Cell -> Bool makePossible, makeFixed :: Data.Word.Word16 -> Cell
isPossible (Possible _) = True makePossible = flip Data.Bits.setBit 15
isPossible _ = False makeFixed = flip Data.Bits.clearBit 15
readGrid :: String -> Maybe Grid readGrid :: String -> Maybe Grid
readGrid s readGrid s
| length s == 81 = traverse (traverse readCell) . Data.List.Split.chunksOf 9 $ s | length s == 81 = Data.Vector.Unboxed.fromList <$> traverse readCell s
| otherwise = Nothing | otherwise = Nothing
where where
allBitsSet = 1022 allBitsSet = 1022
readCell '.' = Just $ Possible allBitsSet readCell '.' = Just $ makePossible allBitsSet
readCell c readCell c
| Data.Char.isDigit c && c > '0' = Just . Fixed . Data.Bits.bit . Data.Char.digitToInt $ c | Data.Char.isDigit c && c > '0' = Just . Data.Bits.bit . Data.Char.digitToInt $ c
| otherwise = Nothing | otherwise = Nothing
fromXY :: (Int, Int) -> Int
fromXY (x, y) = x * 9 + y
allRowIxs, allColIxs, allSubGridIxs, allIxs :: [CellIxs]
allRowIxs = [getRow i | i <- [0..8]]
where getRow n = [ fromXY (n, i) | i <- [0..8] ]
allColIxs = [getCol i | i <- [0..8]]
where getCol n = [ fromXY (i, n) | i <- [0..8] ]
allSubGridIxs = [getSubGrid i | i <- [0..8]]
where getSubGrid n = let (r, c) = (n `quot` 3, n `mod` 3)
in [ fromXY (3 * r + i, 3 * c + j) | i <- [0..2], j <- [0..2] ]
allIxs = concat [allRowIxs, allColIxs, allSubGridIxs]
replaceCell :: Int -> Cell -> Grid -> Grid
replaceCell i c = Data.Vector.Unboxed.modify (\v -> Data.Vector.Unboxed.Mutable.unsafeWrite v i c)
showGrid :: Grid -> String showGrid :: Grid -> String
showGrid = unlines . map (unwords . map showCell) showGrid grid = unlines . map (unwords . map (showCell . (grid !))) $ allRowIxs
where where
showCell (Fixed x) = show . Data.Bits.countTrailingZeros $ x showCell xs
showCell _ = "." | isPossible xs = "."
| otherwise = show . Data.Bits.countTrailingZeros $ xs
showGridWithPossibilities :: Grid -> String showGridWithPossibilities :: Grid -> String
showGridWithPossibilities = unlines . map (unwords . map showCell) showGridWithPossibilities grid = unlines . map (unwords . map (showCell . (grid !))) $ allRowIxs
where where
showCell (Fixed x) = (show . Data.Bits.countTrailingZeros $ x) ++ " " showCell xs
showCell (Possible xs) = | isPossible xs =
"[" ++ map (\i -> if Data.Bits.testBit xs i then Data.Char.intToDigit i else ' ') [1..9] ++ "]" "[" ++ map (\i -> if Data.Bits.testBit xs i then Data.Char.intToDigit i else ' ') [1..9] ++ "]"
| otherwise = (show . Data.Bits.countTrailingZeros $ xs) ++ " "
-- Exclusive Possibilities Accumulator
data ExPosAcc = ExPosAcc ![Int] ![Int] ![Int] ![Int] ![Int] ![Int] ![Int] ![Int] ![Int]
exPosAccEmpty :: ExPosAcc
exPosAccEmpty = ExPosAcc [] [] [] [] [] [] [] [] []
exPosAccInsert :: Int -> Int -> ExPosAcc -> ExPosAcc
exPosAccInsert 1 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc (i:v1) v2 v3 v4 v5 v6 v7 v8 v9
exPosAccInsert 2 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 (i:v2) v3 v4 v5 v6 v7 v8 v9
exPosAccInsert 3 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 (i:v3) v4 v5 v6 v7 v8 v9
exPosAccInsert 4 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 v3 (i:v4) v5 v6 v7 v8 v9
exPosAccInsert 5 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 v3 v4 (i:v5) v6 v7 v8 v9
exPosAccInsert 6 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 v3 v4 v5 (i:v6) v7 v8 v9
exPosAccInsert 7 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 v3 v4 v5 v6 (i:v7) v8 v9
exPosAccInsert 8 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 v3 v4 v5 v6 v7 (i:v8) v9
exPosAccInsert 9 i (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) = ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 (i:v9)
exPosAccInsert _ _ _ = error "Impossible"
exPosAccToList :: ExPosAcc -> [(Int, [Int])]
exPosAccToList (ExPosAcc v1 v2 v3 v4 v5 v6 v7 v8 v9) =
[(1, v1), (2, v2), (3, v3), (4, v4), (5, v5), (6, v6), (7, v7), (8, v8), (9, v9)]
exclusivePossibilities :: [Cell] -> [Data.Word.Word16] exclusivePossibilities :: [Cell] -> [Data.Word.Word16]
exclusivePossibilities row = exclusivePossibilities cells =
row cells
& zip [1..9] & zip ([1..9] :: [Int])
& filter (isPossible . snd) & filter (isPossible . snd)
& Data.List.foldl' & Data.List.foldl'
(\acc ~(i, Possible xs) -> (\acc (i, xs) ->
Data.List.foldl' Data.List.foldl'
(\acc' n -> if Data.Bits.testBit xs n then Map.insertWith prepend n [i] acc' else acc') (\acc' n -> if Data.Bits.testBit xs n then exPosAccInsert n i acc' else acc')
acc acc
[1..9]) [1..9])
Map.empty exPosAccEmpty
& Map.filter ((< 4) . length) & exPosAccToList
& Map.foldlWithKey'(\acc x is -> Map.insertWith prepend is [x] acc) Map.empty & filter ((< 4) . length . snd)
& Data.List.foldl' (\acc (x, is) -> Map.insertWith prepend is [x] acc) Map.empty
& Map.filterWithKey (\is xs -> length is == length xs) & Map.filterWithKey (\is xs -> length is == length xs)
& Map.elems & Map.elems
& map (Data.List.foldl' Data.Bits.setBit Data.Bits.zeroBits) & map (Data.List.foldl' Data.Bits.setBit Data.Bits.zeroBits)
@ -75,63 +127,91 @@ exclusivePossibilities row =
makeCell :: Data.Word.Word16 -> Maybe Cell makeCell :: Data.Word.Word16 -> Maybe Cell
makeCell ys makeCell ys
| ys == Data.Bits.zeroBits = Nothing | ys == Data.Bits.zeroBits = Nothing
| Data.Bits.popCount ys == 1 = Just $ Fixed ys | Data.Bits.popCount (makeFixed ys) == 1 = Just $ makeFixed ys
| otherwise = Just $ Possible ys | otherwise = Just $ makePossible ys
pruneCellsByFixed :: [Cell] -> Maybe [Cell] pruneCells :: MGrid s -> CellIxs -> Control.Monad.ST.ST s (Maybe Bool)
pruneCellsByFixed cells = traverse pruneCell cells pruneCells grid cellIxs = do
cells <- traverse (Data.Vector.Unboxed.Mutable.unsafeRead grid) cellIxs
pruneCells' grid cells cellIxs
pruneCells' :: MGrid s -> [Cell] -> CellIxs -> Control.Monad.ST.ST s (Maybe Bool)
pruneCells' grid cells = Control.Monad.foldM pruneCell' (Just False)
where where
fixeds = setBits Data.Bits.zeroBits [x | Fixed x <- cells] pruneCell' Nothing _ = return Nothing
pruneCell' (Just changed) i = pruneCell i >>= \case
Nothing -> return Nothing
Just changed' -> return $ Just (changed || changed')
pruneCell (Possible xs) = makeCell (xs Data.Bits..&. Data.Bits.complement fixeds)
pruneCell x = Just x
pruneCellsByExclusives :: [Cell] -> Maybe [Cell]
pruneCellsByExclusives cells = case exclusives of
[] -> Just cells
_ -> traverse pruneCell cells
where
exclusives = exclusivePossibilities cells exclusives = exclusivePossibilities cells
allExclusives = setBits Data.Bits.zeroBits exclusives allExclusives = setBits Data.Bits.zeroBits exclusives
fixeds = setBits Data.Bits.zeroBits . filter isFixed $ cells
pruneCell cell@(Fixed _) = Just cell pruneCell i = do
pruneCell cell@(Possible xs) cell <- Data.Vector.Unboxed.Mutable.unsafeRead grid i
| intersection `elem` exclusives = makeCell intersection pruneCellByFixed (i, cell) >>= \case
| otherwise = Just cell Nothing -> return Nothing
Just changed -> do
cell' <- Data.Vector.Unboxed.Mutable.unsafeRead grid i
pruneCellByExclusives (i, cell') >>= \case
Nothing -> return Nothing
Just changed' -> return $ Just (changed || changed')
pruneCellByFixed (i, xs)
| isFixed xs = return $ Just False
| xs' == xs = return $ Just False
| otherwise = case makeCell xs' of
Nothing -> return Nothing
Just cell' -> do
Data.Vector.Unboxed.Mutable.unsafeWrite grid i cell'
return $ Just True
where
xs' = xs Data.Bits..&. Data.Bits.complement fixeds
pruneCellByExclusives (i, xs)
| isFixed xs = return $ Just False
| null exclusives = return $ Just False
| makePossible intersection == xs = return $ Just False
| intersection `elem` exclusives = case makeCell intersection of
Nothing -> return Nothing
Just cell' -> do
Data.Vector.Unboxed.Mutable.unsafeWrite grid i cell'
return $ Just True
| otherwise = return $ Just False
where where
intersection = xs Data.Bits..&. allExclusives intersection = xs Data.Bits..&. allExclusives
pruneCells :: [Cell] -> Maybe [Cell] pruneGrid' :: MGrid s -> Control.Monad.ST.ST s (Maybe Bool)
pruneCells cells = fixM pruneCellsByFixed cells >>= fixM pruneCellsByExclusives pruneGrid' grid = Control.Monad.foldM pruneCells' (Just False) allIxs
where
subGridsToRows :: Grid -> Grid pruneCells' Nothing _ = return Nothing
subGridsToRows = pruneCells' (Just changed) ixs = pruneCells grid ixs >>= \case
concatMap (\rows -> let [r1, r2, r3] = map (Data.List.Split.chunksOf 3) rows Nothing -> return Nothing
in zipWith3 (\a b c -> a ++ b ++ c) r1 r2 r3) Just changed' -> return $ Just (changed || changed')
. Data.List.Split.chunksOf 3
pruneGrid' :: Grid -> Maybe Grid
pruneGrid' grid =
traverse pruneCells grid
>>= fmap Data.List.transpose . traverse pruneCells . Data.List.transpose
>>= fmap subGridsToRows . traverse pruneCells . subGridsToRows
pruneGrid :: Grid -> Maybe Grid pruneGrid :: Grid -> Maybe Grid
pruneGrid = fixM pruneGrid' pruneGrid grid = Control.Monad.ST.runST $ do
mGrid <- Data.Vector.Unboxed.unsafeThaw grid
fix mGrid
where
fix mg = pruneGrid' mg >>= \case
Nothing -> return Nothing
Just True -> fix mg
Just False -> Just <$> Data.Vector.Unboxed.unsafeFreeze mg
isGridFilled :: Grid -> Bool isGridFilled :: Grid -> Bool
isGridFilled grid = null [ () | Possible _ <- concat grid ] isGridFilled = not . Data.Vector.Unboxed.any isPossible
isGridInvalid :: Grid -> Bool isGridInvalid :: Grid -> Bool
isGridInvalid grid = isGridInvalid grid =
any isInvalidRow grid any isInvalidRow (map (map (grid !)) allRowIxs)
|| any isInvalidRow (Data.List.transpose grid) || any isInvalidRow (map (map (grid !)) allColIxs)
|| any isInvalidRow (subGridsToRows grid) || any isInvalidRow (map (map (grid !)) allSubGridIxs)
where where
isInvalidRow row = isInvalidRow row =
let fixeds = [x | Fixed x <- row] let fixeds = filter isFixed row
emptyPossibles = [() | Possible x <- row, x == Data.Bits.zeroBits] emptyPossibles = filter (== 32768) . filter isPossible $ row
in hasDups fixeds || not (null emptyPossibles) in hasDups fixeds || not (null emptyPossibles)
hasDups l = hasDups' l [] hasDups l = hasDups' l []
@ -143,27 +223,22 @@ isGridInvalid grid =
nextGrids :: Grid -> (Grid, Grid) nextGrids :: Grid -> (Grid, Grid)
nextGrids grid = nextGrids grid =
let (i, first@(Fixed _), rest) = let (i, first, rest) =
fixCell fixCell
. Data.List.minimumBy (compare `Data.Function.on` (possibilityCount . snd)) . Data.Vector.Unboxed.minimumBy (compare `Data.Function.on` (possibilityCount . snd))
. filter (isPossible . snd) . Data.Vector.Unboxed.imapMaybe (\j cell -> if isPossible cell then Just (j, cell) else Nothing)
. zip [0..]
. concat
$ grid $ grid
in (replace2D i first grid, replace2D i rest grid) in (replaceCell i first grid, replaceCell i rest grid)
where where
possibilityCount (Possible xs) = Data.Bits.popCount xs possibilityCount xs
possibilityCount (Fixed _) = 1 | isPossible xs = Data.Bits.popCount xs - 1
| otherwise = 1
fixCell ~(i, Possible xs) = fixCell (i, xs) =
let x = Data.Bits.countTrailingZeros xs let x = Data.Bits.countTrailingZeros xs
in case makeCell (Data.Bits.clearBit xs x) of in case makeCell (Data.Bits.clearBit xs x) of
Nothing -> error "Impossible case" Nothing -> error "Impossible case"
Just cell -> (i, Fixed (Data.Bits.bit x), cell) Just cell -> (i, Data.Bits.bit x, cell)
replace2D :: Int -> a -> [[a]] -> [[a]]
replace2D i v = let (x, y) = (i `quot` 9, i `mod` 9) in replace x (replace y (const v))
replace p f xs = [if i == p then f x else x | (x, i) <- zip xs [0..]]
solve :: Grid -> Maybe Grid solve :: Grid -> Maybe Grid
solve grid = pruneGrid grid >>= solve' solve grid = pruneGrid grid >>= solve'
@ -177,10 +252,14 @@ solve grid = pruneGrid grid >>= solve'
main :: IO () main :: IO ()
main = do main = do
inputs <- lines <$> getContents grids <- lines <$> getContents
Control.Monad.forM_ inputs $ \input -> let solutions = parMap readAndSolve grids
case readGrid input of putStrLn $
Nothing -> putStrLn "Invalid input" show (length $ filter Data.Maybe.isJust solutions) ++ "/" ++ show (length grids) ++ " solved"
Just grid -> case solve grid of where
Nothing -> putStrLn "No solution found" readAndSolve grid = case readGrid grid of
Just grid' -> putStrLn $ showGrid grid' Nothing -> Nothing
Just b -> solve b
chunkSize = 1000
parMap f = withStrategy (parBuffer chunkSize rdeepseq) . map f