Search code examples
haskellsudoku

Find 9 3x3 "blocks" of Sudoku board (Haskell)


I am learning Haskell and I decided to embark on a small sudoku solver as a project. I have been using this assignment as a guide and I recently hit a wall on sub-problem D2 which is to create a function that will generate the sudoku blocks (9 3x3 grids) from a Sudoku board (a 9x9 grid).

I started writing the following code but I quickly realized it was a terrible idea. It is clunky code that is not idiomatic Haskell (in my opinion) and completely violates the DRY principle.

type Block = [Maybe Int]
data Sudoku = Sudoku [[Maybe Int]]

blocks :: Sudoku -> [Block]
blocks (Sudoku rs) = block1 : block2 : block3 : block4 : block5 : block6 : block7 : block8 : block9 : []
  where block1 = [(rs!!0)!!0] ++ [(rs!!0)!!1] ++ [(rs!!0)!!2] ++ [(rs!!1)!!0] ++ [(rs!!1)!!1] ++ [(rs!!1)!!2]++ [(rs!!2)!!0] ++ [(rs!!2)!!1] ++ [(rs!!2)!!2]  
        block2 = ...
        block3 = ...
        ...

I was wondering how to write a more concise and idiomatic function to complete the task? How would you implement it? Any ideas are appreciated!

I have also consulted this previous and possibly related SO question but I was not sure how to convert the pythonic solution to Haskell and whether or not I even should. I have also seen this question but I have structured my Sudoku board differently.

All of my current code can be found here. Also, let me know if I can clarify anything.


Solution

  • First, write a groupBy3 function which groups a list by three elements:

    groupBy3 :: [a] -> [[a]]
    

    Then use the following chain of operations:

    • map groupBy3
    • transpose
    • concat
    • groupBy3
    • map concat

    Written on my phone, so it's untested but it should be close.

    Update: I verified that this works:

    groupBy3 (a:b:c:ds) = [a,b,c] : groupBy3 ds
    groupBy3 []         = []
    groupBy3 as         = [ as ]  -- won't happen
    
    boxes =  map concat . groupBy3 . concat . transpose . map groupBy3
    
    grid = [ [ [i,j] | j <- ['1'..'9'] ] | i <- ['a'..'i'] ]
    
    test1 = boxes grid