-- Returns every solution of the 4x4 sudoku.
-- Provide two functions to solve the problem.
-- One is bruteForceSolve using the brute force method.
-- The optimizeSolve is the optimized version of the bruteForceSolve.
-- These two functions will return all solution of the 4x4 sudoku.
-- If there is no valid solution of the puzzle,function will return [].
-- Usage: 1. Load the haskell file :l sudoku.hs
-- 2. bruteForceSolve input (brute force method)
-- e.g bruteForceSolve [[3,4,0,0],[2,0,3,0],[0,3,0,2],[0,0,1,3]]
-- 3. optimizedSolve input (optimized version)
-- e.g optimizeSolve [[3,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,1,0]]
-- 4. Using bruteForceSolve examplePuzzle or optimizedSolve multipleSolutionsPuzzle for test.
--Main>bruteForceSolve [[3,4,0,0],[2,0,3,0],[0,3,0,2],[0,0,1,3]]
--[[[3,4,2,1],[2,1,3,4],[1,3,4,2],[4,2,1,3]]]
--Main>optimizeSolve [[3,0,0,0],[0,2,0,0],[4,0,0,3],[0,0,1,0]]
--[]
--empty list if there is no solution
--Main>optimizeSolve multipleSolutionsPuzzle
--[[[3,1,2,4],[2,4,3,1],[1,3,4,2],[4,2,1,3]],[[3,1,4,2],[2,4,3,1],[1,3,2,4],[4,2,1,3]],[[3,4,2,1],[2,1,3,4],[1,3,4,2],[4,2,1,3]]]
import Data.List
{-
The input is a matrix, represented as a list
of rows, where each row is a list of the numbers
in the grid. The numbers are either 1, 2, 3, 4 and
0 represent the field is empty.
According to the description above,I construct the following
types.
-}
type Grid = Matrix Value
type Matrix a = [Row a]
type Row a = [a]
type Value = Int
--The box size of the 4x4 sudoku is 2
boxSize :: Int
boxSize = 2
--the element of the 4x4 sudoku is 1,2,3,4
values :: [Value]
values = [1 .. 4]
--the value 0 indicating an empty cell.
empty :: Value -> Bool
empty = (== 0)
--The sample input here
examplePuzzle :: Grid
examplePuzzle = [[3,4,0,0],
[2,0,3,0],
[0,3,0,2],
[0,0,1,3]]
--The example of multiple solutions puzzle
multipleSolutionsPuzzle :: Grid
multipleSolutionsPuzzle =
[[3,0,0,0],
[2,0,3,0],
[0,3,0,0],
[0,0,1,3]]
{-Determining whether a grid is solved or not
our rows,columns and boxes must have all the digits(1,2,3,4)
without duplicates.validGrid function extract rows,columns and boxes and
check this criteria.
-}
validGrid :: Grid -> Bool
validGrid g = all noDups (rows g) &&
all noDups (cols g) &&
all noDups (boxes g)
--noDups function iterate the list and determine whether the
--list have duplicate elements or not
noDups :: (Eq a, Num a) => [a] -> Bool
noDups [] = True
--list like [1,2,0,0] should be True
noDups (x : xt) = if x == 0 then (noDups xt) else (not (elem x xt) && noDups xt)
--The next three is extraction function
rows :: Matrix a -> [Row a]
rows = id
--columns are the transpose of the rows
cols :: Matrix a -> [Row a]
cols = transpose
--let the element from one box in one sublist,transpose the element in pairs
boxes :: Matrix a -> [Row a]
boxes = unpack . map transpose . pack
where
-- pack the puzzle list in pairs
--[[[[3,4],[0,0]],[[2,0],[3,0]]],[[[0,3],[0,2]],[[0,0],[1,3]]]]
--map the transpose to the subelements like [[[3,4],[0,0]],[[2,0],[3,0]]]
--then concat the subelement
pack = split . map split
split = chop boxSize
unpack = map concat . concat
--recombine the list
--chop 2 [1,2,3,4] = [[1,2],[3,4]]
chop :: Int -> [a] -> [[a]]
chop n [] = []
chop n xs = take n xs : chop n (drop n xs)
--a list of grids with every possibility for the empty squares filled in.
--a combination of deterministic and non-deterministic elements
collapse :: Matrix [a] -> [Matrix a]
collapse = sequence . map sequence
--map the choice function into every list
choices g = map (map choice) g
where
--retain non-zero element and replace the 0 with [1..4]
choice v = if empty v then values else [v]
--iterate all the possible grids,get all grids that meet the criterion of 4x4 sudoku
--The brute force solution
bruteForceSolve :: Grid -> [Grid]
bruteForceSolve = filter validGrid . collapse . choices
--Optimize
--Enumerating all digits for empty spaces doesn’t take into account the constraints of the puzzle itself.
{-It take so long to solve the problem like
[[3,0,0,0],
[2,0,0,0],
[0,3,0,0],
[0,0,1,3]]
For each non-deterministic cell, we can throw away any inconsistent choice.
-}
--throw away inconsistent choices in rows,columns and boxes
optimize = optimizeIn boxes . optimizeIn cols . optimizeIn rows
where optimizeIn f = f . map reduce . f
--determine it is a single element list or not
single :: [a] -> Bool
single [_] = True
single _ = False
--throw away inconsistent choice.
--reduce [[3],[4],[1,2,3,4],[1,2,3,4]] = [[3],[4],[1,2],[1,2]]
reduce xss = [xs `minus` singles | xs <- xss]
--singles choose all single elements and concatenate them
where singles = concat (filter single xss)
{-if there are only one element,eg. [3] in [[3],[4],[1,2,3,4],[1,2,3,4]],remain unchanged,
if there are more than one element eg. [1,2,3,4] in [[3],[4],[1,2,3,4],[1,2,3,4]],throw away
element dupulicate in singles
-}
xs `minus` ys = if single xs then xs else xs \\ ys
--a list of grids with every possibility for the empty squares filled in.
--a combination of deterministic and non-deterministic elements,the non-deterministic elements here are reduced
--iterate all the possible grids,get all grids that meet the criterion of 4x4 sudoku
optimizeSolve :: Grid -> [Grid]
optimizeSolve = filter validGrid . collapse . optimize . choices