exact-cover-0.1.0.0: examples/sudoku/Sudoku.hs
-- |
module Sudoku
(
-- * Types
Digit(..), allDigits
, SRow(..), allRows
, SCol(..), allColumns
, SBox(..), box
, SConstraint(..)
-- * Map Sudoku to exact-cover problem
, toConstraints
, convert2SolvedGrid
-- * Reading Sudokus
, readLineFormat
, readPEulerGrid
)
where
import Sudoku.Grid
import Control.Arrow (second)
import Safe (readMay, headMay)
import qualified Data.Vector.Generic as V (generate)
import Data.Map (Map)
import qualified Data.Map.Strict as Map (lookup, fromList)
import Data.Set (Set)
import qualified Data.Set as Set (fromList)
data Digit = D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9
deriving (Eq, Ord, Enum)
instance Show Digit where
show D1 = "1"
show D2 = "2"
show D3 = "3"
show D4 = "4"
show D5 = "5"
show D6 = "6"
show D7 = "7"
show D8 = "8"
show D9 = "9"
instance Read Digit where
readsPrec _ ('1':rest) = [(D1, rest)]
readsPrec _ ('2':rest) = [(D2, rest)]
readsPrec _ ('3':rest) = [(D3, rest)]
readsPrec _ ('4':rest) = [(D4, rest)]
readsPrec _ ('5':rest) = [(D5, rest)]
readsPrec _ ('6':rest) = [(D6, rest)]
readsPrec _ ('7':rest) = [(D7, rest)]
readsPrec _ ('8':rest) = [(D8, rest)]
readsPrec _ ('9':rest) = [(D9, rest)]
readsPrec _ _ = []
allDigits :: [Digit]
allDigits = [toEnum x | x <- [0..8]]
data SRow = R0 | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8
deriving (Show, Eq, Ord, Enum)
allRows :: [SRow]
allRows = [toEnum x | x <- [0..8]]
data SCol = C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8
deriving (Show, Eq, Ord, Enum)
allColumns :: [SCol]
allColumns = [toEnum x | x <- [0..8]]
data SBox = B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8
deriving (Show, Eq, Ord, Enum)
-- | Given the row and column, get the box.
box :: SRow -> SCol -> SBox
box (fromEnum -> r) (fromEnum -> c) =
toEnum $ (r `div` 3) * 3 + (c `div` 3)
data SConstraint = RowColumn !SRow !SCol
| RowNumber !SRow !Digit
| ColumnNumber !SCol !Digit
| BoxNumber !SBox !Digit
deriving (Show, Eq, Ord)
instance Enum SConstraint where
fromEnum (RowColumn r c) = fromEnum r * 9 + fromEnum c
fromEnum (RowNumber r n) = 81 + fromEnum r * 9 + fromEnum n
fromEnum (ColumnNumber c n) = 162 + fromEnum c * 9 + fromEnum n
fromEnum (BoxNumber b n) = 243 + fromEnum b * 9 + fromEnum n
toEnum x = case second (`divMod` 9) $ x `divMod` 81 of
(a, (b, c)) | a == 0 -> RowColumn (toEnum b) (toEnum c)
| a == 1 -> RowNumber (toEnum b) (toEnum c)
| a == 2 -> ColumnNumber (toEnum b) (toEnum c)
| a == 3 -> BoxNumber (toEnum b) (toEnum c)
| otherwise -> error "toEnum instance error."
-- | Maps the Sudoku problem to a set of constraints following
-- https://en.wikipedia.org/wiki/Exact_cover#Sudoku.
toConstraints :: Grid (Maybe Digit)
-> Map (Set SConstraint) (SRow, SCol, Digit)
toConstraints (toListList -> g) =
let expanded :: [(SRow, SCol, Maybe Digit)]
expanded = concatMap (\(r, xs) -> zip3 (repeat r) allColumns xs)
. zip allRows $ g
in Map.fromList $ concatMap f expanded
where
f :: (SRow, SCol, Maybe Digit)
-> [((Set SConstraint), (SRow, SCol, Digit))]
f (r, c, Just n) = [clueExist r c n]
f (r, c, Nothing) = clueNotExist r c
clueExist :: SRow -> SCol -> Digit
-> ((Set SConstraint), (SRow, SCol, Digit))
clueExist r c n = let constraint = Set.fromList [ RowColumn r c
, RowNumber r n
, ColumnNumber c n
, BoxNumber (box r c) n
]
in (constraint, (r, c, n))
clueNotExist :: SRow -> SCol
-> [((Set SConstraint), (SRow, SCol, Digit))]
clueNotExist r c = map (clueExist r c) allDigits
convert2SolvedGrid :: [(SRow, SCol, Digit)] -> Grid Digit
convert2SolvedGrid xs =
let m = Map.fromList . map (\(r,c,n) -> ((r,c),n)) $ xs
in Grid $ V.generate 9 $ \r ->
V.generate 9 $ \c ->
case Map.lookup (toEnum r, toEnum c) m of
Just n -> n
Nothing -> error "convert2SolvedGrid error."
-- | Parse single line Sudoku format.
readLineFormat :: String -> Grid (Maybe Digit)
readLineFormat (headMay . lines -> Just str) | length str == 81 =
fromListList . chunksOf 9 . map (\c -> readMay [c]) $ str
where
chunksOf :: Int -> [a] -> [[a]]
chunksOf _ [] = [[]]
chunksOf n xs@(_:_) = let (y, rest) = splitAt n xs
in y : chunksOf n rest
readLineFormat _ = error "Invalid Sudoku grid."
-- | Parse project Euler Sudoku format.
readPEulerGrid :: String -> Grid (Maybe Digit)
readPEulerGrid (lines -> strS) | length strS >= 9 =
fromListList $ map readPEulerLine strS
where
readPEulerLine :: String -> [Maybe Digit]
readPEulerLine (take 9 -> strLn) = map (\c -> readMay [c]) strLn
readPEulerGrid _ = error "Invalid Sudoku grid."