sodokuSolver/app/Main.hs

209 lines
7 KiB
Haskell
Raw Permalink Normal View History

2023-03-24 23:48:24 +01:00
module Main(main) where
import Data.Array
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS
import Control.Monad ((>=>))
import Control.Applicative ((<|>))
import Data.Char
-- import Debug.Trace
import Data.List (foldl')
import Data.Maybe(fromMaybe)
data Cell = Fixed Int | Possible IntSet
deriving (Show, Eq)
type Grid = Array (Int,Int) Cell
cellToChar :: Cell -> Char
cellToChar (Fixed n) = intToDigit n
cellToChar (Possible _) = '.'
showCell :: Cell -> String
showCell (Fixed x) = show x ++ " "
showCell (Possible is) = (++ "]") . foldl' (\acc x -> acc ++ if x `IS.member` is then show x else " ") "[" $ [1..9]
showGrid :: Grid -> String
showGrid = unlines . fmap (fmap cellToChar) . chunksOf 9 . elems
showGridWithPossibilities :: Grid -> String
showGridWithPossibilities = unlines . map (unwords . map showCell) . rows
-- I should write index functions for rows and columns
-- then I could make an abstract access functions and define
-- the concrete ones using the index function and the abstract
-- accessors (same for the update ones)
-- (like prune pruneStructures)
row :: Grid -> Int -> [Cell]
row grid rn = [grid ! (rn,i) | i <- [0..8]]
rows :: Grid -> [[Cell]]
rows grid = [row grid i | i <- [0..8]]
column :: Grid -> Int -> [Cell]
column grid rn = [grid ! (i,rn) | i <- [0..8]]
columns :: Grid -> [[Cell]]
columns grid = [column grid i | i <- [0..8]]
sGrid :: Grid -> Int -> [Cell]
sGrid grid sGridn = [grid ! (a,b) | (a,b) <- intToSGindex sGridn]
sGrids :: Grid -> [[Cell]]
sGrids grid = [sGrid grid i | i <- [0..8]]
{-# INLINE intToSGindex #-}
intToSGindex :: Int -> [(Int,Int)]
intToSGindex 0 = [(x,y) | x <- [0..2], y <- [0..2] ]
intToSGindex 1 = [(x,y) | x <- [0..2], y <- [3..5] ]
intToSGindex 2 = [(x,y) | x <- [0..2], y <- [6..8] ]
intToSGindex 3 = [(x,y) | x <- [3..5], y <- [0..2] ]
intToSGindex 4 = [(x,y) | x <- [3..5], y <- [3..5] ]
intToSGindex 5 = [(x,y) | x <- [3..5], y <- [6..8] ]
intToSGindex 6 = [(x,y) | x <- [6..8], y <- [0..2] ]
intToSGindex 7 = [(x,y) | x <- [6..8], y <- [3..5] ]
intToSGindex 8 = [(x,y) | x <- [6..8], y <- [6..8] ]
intToSGindex _ = []
pruneCells :: [Cell] -> Maybe [Cell]
pruneCells cells = traverse pruneCell cells
where
fixeds = IS.fromList [x | Fixed x <- cells]
pruneCell (Possible xs) = t $ xs IS.\\ fixeds
pruneCell (Fixed x) = Just $ Fixed x
t x | IS.size x == 0 = Nothing
t x | IS.size x == 1 = Just $ Fixed $ head $ IS.toList x
t x | otherwise = Just $ Possible x
checkConstraint :: Grid -> Maybe Grid
checkConstraint grid = case and [abs (x - y ) > 1 | i <- [0..7], j <- [0..8], Fixed x <- [grid ! (i,j)], Fixed y <- [grid ! (i+1,j)]] && and [abs (x - y) > 1 | i <- [0..8], j <- [0..7], Fixed x <- [grid ! (i,j)], Fixed y <- [grid ! (i,j+1)]] of
True -> Just grid
False -> Nothing
updateRow :: Grid -> [Cell] -> Int -> Grid
updateRow grid cells rn = grid // zip [(rn,i) | i <- [0..8]] cells
updateRows :: Grid -> [[Cell]] -> Grid
updateRows grid cellss = grid // concat (zipWith f [0..8] cellss)
where
f i = zip [(i,x)| x <- [0..8]]
updateColumn :: Grid -> [Cell] -> Int -> Grid
updateColumn grid cells cn = grid // zip [(i,cn) | i <- [0..8]] cells
updateColumns :: Grid -> [[Cell]] -> Grid
updateColumns grid cells = grid // concat (zipWith f [0..8] cells)
where
f i = zip [(x,i)| x <- [0..8]]
updateSGrid :: Grid -> [Cell] -> Int -> Grid
updateSGrid grid cells sGn = grid // zip (intToSGindex sGn) cells
updateSGrids :: Grid -> [[Cell]] -> Grid
updateSGrids grid cells = grid // concat (zipWith f [0..8] cells)
where
f i = zip (intToSGindex i)
{-# INLINE pruneStructures #-}
pruneStructures :: (Grid -> [[Cell]]) -> (Grid -> [[Cell]] -> Grid) -> Grid -> Maybe Grid
pruneStructures access update grid = do
let rs = access grid
prs <- traverse pruneCells rs
return $ update grid prs
pruneRows :: Grid -> Maybe Grid
pruneRows = pruneStructures rows updateRows
pruneColummns :: Grid -> Maybe Grid
pruneColummns = pruneStructures columns updateColumns
pruneSGrids :: Grid -> Maybe Grid
pruneSGrids = pruneStructures sGrids updateSGrids
pruneGrid :: Grid -> Maybe Grid
pruneGrid = pruneRows >=> pruneColummns >=> pruneSGrids
fixGridMaybe :: (Grid -> Maybe Grid) -> Grid -> Maybe Grid
fixGridMaybe f grid = do
x <- f grid
if x == grid
then return grid
else fixGridMaybe f x
prunesGrid :: Grid -> Maybe Grid
prunesGrid = fixGridMaybe pruneGrid
isFixed :: Cell -> Bool
isFixed (Fixed _) = True
isFixed _ = False
isPossible :: Cell -> Bool
isPossible (Possible _) = True
isPossible _ = False
allFixed :: Grid -> Bool
allFixed grid = all isFixed $ elems grid
headL :: [a] -> [a]
headL [] = []
headL (x:_) = [x]
travTuple :: (Maybe a, Maybe b) -> Maybe (a,b)
travTuple (Nothing, _) = Nothing
travTuple (_,Nothing) = Nothing
travTuple (Just x, Just y) = Just (x,y)
makeChoice :: Grid -> (Grid,Grid)
makeChoice grid = splitGrid
where
smallestCellInd :: ((Int,Int),[Int])
smallestCellInd = convert . indHelper $ (assocs grid)
convert (c,Possible is) = (c,IS.toList is)
convert (_,Fixed _) = error "convert error"
indHelper [] = error "We have no choices but aren't done?"
indHelper ((_,Fixed _) : xs) = indHelper xs
indHelper (t@(_,Possible _) : xs) = indHelper' t xs
indHelper' :: ((Int,Int),Cell) -> [((Int,Int),Cell)] -> ((Int,Int),Cell)
indHelper' t [] = t
indHelper' p@(_,Possible is) (p'@(_, Possible is') : xs) = if IS.size is <= IS.size is' then indHelper' p xs else indHelper' p' xs
indHelper' p ((_,(Fixed _)) : xs) = indHelper' p xs
indHelper' (_,Fixed _) _ = error "indHelper' impossible branch"
splitGrid :: (Grid,Grid)
splitGrid = case smallestCellInd of
(c,[x,y]) -> (grid // [(c,Fixed x)], grid // [(c,Fixed y)])
(c,x:t@(_:_)) -> (grid // [(c,Fixed x)], grid // [(c,Possible $ IS.fromList t)])
_ -> error "We just checked the size"
solveSodoku :: Grid -> Maybe Grid
solveSodoku grid = do
nGrid <- prunesGrid grid
nGrid' <- checkConstraint nGrid
if allFixed nGrid'
then return nGrid
else do
let (x,y) = makeChoice nGrid
solveSodoku x <|> solveSodoku y
readSodoku :: String -> Maybe Grid
readSodoku str | length str == 81 = fmap (listArray ((0,0),(8,8))) . traverse readCell $ str
| otherwise = Nothing
where
readCell :: Char -> Maybe Cell
readCell '.' = Just . Possible . IS.fromList $ [1..9]
readCell c | isDigit c && c > '0' = Just . Fixed . digitToInt $ c
| otherwise = Nothing
chunksOf :: Int -> [a] -> [[a]]
chunksOf _ [] = []
chunksOf n xs = let (c,txs) = splitAt n xs in c : chunksOf n txs
{-# NOINLINE sodokuStr #-}
sodokuStr :: String
sodokuStr = "....4.....9.....6...4.9.1..............1.3..............3.6.2...2.....4.....7...."
main :: IO ()
main = do
putStrLn $ fromMaybe "Unsolvable" $ do
sodoku <- readSodoku sodokuStr
solution <- solveSodoku sodoku
return $ showGrid solution