Sudoku Zipper

By kapilash

I will post about an interesting idiom in Functional Programming , that I came across recently – Zipper. zipper provides us with
the ability to traverse pure functional programming data structures with surprising agility and efficiency , without losing the purity.

module Main where

import Sudoku.Parser as P

import Sudoku.Data as D

import System.Environment(getArgs)

import Data.Tree

import Data.Tree.Zipper

The imports are similar to the earlier one but with an additional module loaded – the zipper.

There’s just a minor change in the main, we pass the sudoku to zipperSol, which solves the sudoku and prints the solution.

main = do args <- getArgs

          case args of

            (f:[])   -> do x <- parseSudokuFromFile f

                           zipperSol x

            _        -> do usage

usage = do print "Usage:"

           print "STest <file>"

We create the tree the way we did earlier.

sudokuTree s = Node s (map sudokuTree (explore s))

explore s =  case (getUnfilledCell s) of

               Just (c,pos) -> filter validSudoku [ (fillSudoku s pos val) |  val <- getCells c ]

               Nothing      -> []

getCells (Ttv  lst) = map Sld lst

getCells  x =     [x]

Then, pass it on to the fromTree function of Data.Tree.Zipper module to get a zipper. Thus the below function takes a Sudoku and
constructs a zipper out of it.

sudokuZipper s = fromTree $ sudokuTree s

Now the solution is using Depth First Search. Search for nodes in the tree in depth first manner and if a solution is found, print it and otherwise
report inability.

zipperSol s = case (dfs $ sudokuZipper s) of

                Nothing -> do print "No solution found :-(  "

                (Just x) ->do print  x

The function to perform depth first search is simple too. Given a treeLocation, we check if it is a leaf. If it is a leaf, we need to handle it differently.
Otherwise, we call dfs on the first child of the tree in a recursive manner.
The different handling of the leaf node is this: see, if it is a solution. If it is, report it. Otherwise, delete this leaf and perform the depth first
search on the new tree formed.
Data.Tree.Zipper.delete function returns a May be TreeLoc. The new location will be the right sibling of the treeLoc that we deleted or, if
there’s no right sibling, the parent (which will become a leaf).
it is a leaf node.

dfs :: TreeLoc Sudoku -> Maybe Sudoku

dfs treeLoc = case firstChild treeLoc of

                 Nothing  -> handleLeafNode treeLoc

                 Just x   -> dfs x

handleLeafNode treeLoc =

    case (solvedLoc treeLoc) of

      Just solution -> Just solution

      Nothing ->  case (delete treeLoc) of

                   Nothing -> Nothing

                   (Just x) -> dfs x

solvedLoc :: TreeLoc Sudoku -> Maybe Sudoku

solvedLoc (Loc (Node x _) _ _ _) = if (hasEmptyCells x) then

                                      Nothing

                                   else (Just x)

hasEmptyCells s = case (getUnfilledCell s) of

                    Nothing  -> False

                    Just _   -> True

Elegant and cool. This program is slightly slower than my earlier one ( it takes 9 seconds to solve a sudoku that was solved in 8 seconds by the
earlier, tree based one) and the memory consumption is slightly on the higher side as well. However I can choose my search strategy.
I would be able to move from depth first search to breadth first search or best first search without breaking a sweat.

Leave a Reply