2

I'm trying to build a backtrack sudoku solver in Haskell. But I'm stuck at one of the final points. I created a function called nextBoards which returns all possible soduko boards where the next empty place is filled in with the possible values. Now I'm trying to use backtracking in Haskell, but I cannot use things like while-loops and now I'm really confused on how to do this. I've made a sudoku solver before in Java, but I'm completely stuck on how to do it in Haskell for the moment.

-- Generate the next boards for a given board
nextBoards :: Board -> [Board]
nextBoards b = 
    let position = findFirstEmpty b
    in [update z (snd position) (fst position) b | z <- options b (snd position) (fst position)]

-- We found the real solution
solve :: Board -> [Board] -> Board
solve focus options
    | not (notEmpty focus) = focus
-- We hit a dead path try the next option
solve focus options
    | solve (head options) (tail options)
-- We are solving the focus, generate the next boards
-- and save the rest in the options
solve focus options
    | solve (head (nextBoards focus)) (tail (nextBoards focus))

I really don't know on how to proceed.

1 Answers1

2

You could implement a backtracking solution over a (partial) solution space (like all possible Boards) S with a type signature like:

backtrack :: S -> [S]

With S the current state, and [S] the list of all valid boards. Now there are in general three possible options:

  • We have found a state that is solved, we return a list (singleton) containing our solution:

    backtrack s | solved s = [s]
    
  • We have found a dead trail, in that case we no longer put effort into it, and return an empty list:

                | invalid s = []
    
  • or it is possible we have to deploy our solution further, we generate children: states that have advanced one step from s, and call backtrack recursively, we return the concat of all the backtracks of these children:

                | otherwise = concatMap backtrack $ children s
    

Or putting it all together:

backtrack :: S -> [S]
backtrack s | solved s = [s]
            | invalid s = []
            | otherwise = concatMap backtrack $ children s

The invalid guard can be omitted by simply generating an empty list of children for invalid states (as your solution will probably do) or by preventing to ever generate invalid Boards.

Now grounding this for your problem, generating children would come down to your nextBoards function:

children = nextBoards

or prettifying your function a bit:

children :: Board -> [Board]
children s = [update s y x v | v <- options s y x]
    where (x,y) = findFirstEmpty s

Now the solve (or backtrack) method is defined as:

backtrack :: Board -> [Board]
backtrack s | not (notEmpty s) = [s]
            | otherwise = concatMap backtrack $ children s

backtrack will generate a list of all valid solved Sudoku boards (where the originally places filled in, remain filled in). The list is generated lazily, so if you want one valid solution, you can simply use head:

solve :: Board -> Board
solve = head . backtrack
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555