packages feed

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."