packages feed

picosat-0.1.4: test/Sudoku.hs

import Picosat ( solve, Solution(..) )

cross :: [a] -> [(a, a)]
cross list = [(x,y) | x <- list, y <- list]

rows, cols :: [[(Int, Int)]]
rows = [[(i,j) | i <- [0..8]] | j <- [0..8]]
cols = [[(i,j) | j <- [0..8]] | i <- [0..8]]

c2v :: (Int, Int, Int) -> Int
c2v (i,j,k) = (k-1)*81 + i*9 + j + 1

v2c :: Int -> (Int, Int, Int)
v2c x = ((i `mod` 81) `div` 9, i `mod` 9, (i `div` 81)+1)
  where i = x - 1

group :: [(Int, Int)] -> [[Int]]
group grp = foldr ((:).label) [] [1..9]
  where label k = map c2v [(i,j,k) | (i,j) <- grp ]

square :: [[(Int, Int)]]
square = [quadrent i j | (i,j) <- cross [0..2]]
  where quadrent x y = [(x*3+i,y*3+j) | (i,j) <- cross [0..2]]

oneLabel :: (Int, Int) -> [[Int]]
oneLabel (i,j) = atLeastOne : lessThan2
  where notBoth (c1,c2) = [- c2v (i,j,c1), - c2v (i,j,c2)]
        lessThan2  = map notBoth $ [(a,b) | (a,b) <- cross [1..9], a /= b]
        atLeastOne = map c2v [(i,j,k) | k <- [1..9]]

validLabeling :: [[Int]]
validLabeling = foldr ((++) . oneLabel) [] (cross [0..8])

goodLabeling :: [[Int]]
goodLabeling = foldr ((++) . group) [] (square ++ rows ++ cols)

sudokuForm :: [(Int, Int, Int)] -> [[Int]]
sudokuForm cells = validLabeling ++ goodLabeling ++ (map consClause cells)
  where consClause cell = [c2v cell]

getConstraints :: [[Int]] -> [(Int, Int, Int)]
getConstraints matrix = filter (\(_,_,a) -> a > 0) cells
  where flat = foldl1 (++) matrix
        cells = zip3 [i `div` 9 | i <- [0..]] (cycle [0..8]) flat

toSudoku :: [Int] -> [[Int]]
toSudoku [] = []
toSudoku sol = [[lkup i j | j <- [0..8]]
                          | i <- [0..8]]
  where
    cells = map v2c $ filter ((<) 0) sol
    lkup i j = label $ head $ filter (\(a,b,_) -> a == i && b == j) cells
    label (_,_,c) = c

stringToMatrix :: String -> [[Int]]
stringToMatrix = map (map $ read . return) . lines

puzzle :: String
puzzle = unlines [
      "004000000"
    , "000030002"
    , "390700080"
    , "400009001"
    , "209801307"
    , "600200008"
    , "010008053"
    , "900040000"
    , "000000800"
  ]

main :: IO ()
main = do
  let cnf = sudokuForm $ getConstraints $ stringToMatrix puzzle
  sol <- solve cnf
  case sol of
    Solution s -> mapM_ print $ toSudoku s
    _          -> putStrLn "Puzzle not solvable."