欢迎访问宙启技术站
智能推送

构建一个Haskell程序来解决数独问题

发布时间:2023-12-10 12:23:25

数独是一种逻辑游戏,其中需要填写一个9x9的网格,使得每一行、每一列和每一个3x3的子网格都包含数字1-9,且每个数字只能出现一次。

要解决数独问题,我们可以使用递归和回溯算法来填写空白格子。

下面是一个用Haskell编写的解数独的程序:

import qualified Data.Vector as V

type Grid = V.Vector (V.Vector Int)

-- 检查一个数字是否可以填入指定的格子
isValid :: Grid -> (Int, Int) -> Int -> Bool
isValid grid (row, col) num =
  let rowVals = grid V.! row
      colVals = V.map (V.! col) grid
      squareVals = [grid V.! i V.! j | i <- [row'..row'+2], j <- [col'..col'+2]]
      row' = (row div 3) * 3
      col' = (col div 3) * 3
  in num notElem rowVals && num notElem colVals && num notElem squareVals

-- 在指定的格子中填入一个数字
updateGrid :: Grid -> (Int, Int) -> Int -> Grid
updateGrid grid (row, col) num =
  grid V.// [(row, (grid V.! row) V.// [(col, num)])]

-- 找到下一个空白格子的坐标
nextEmpty :: Grid -> Maybe (Int, Int)
nextEmpty grid =
  let indexes = [(i, j) | i <- [0..8], j <- [0..8]]
  in foldl (\acc index@(row, col) -> if grid V.! row V.! col == 0 then Just index else acc) Nothing indexes

-- 解数独
solveSudoku :: Grid -> Maybe Grid
solveSudoku grid =
  case nextEmpty grid of
    Nothing -> Just grid -- 所有格子都已填满,解题成功
    Just (row, col) ->
      let possibleNums = filter (isValid grid (row, col)) [1..9]
      in foldl (\acc num -> case acc of
                        Nothing -> solveSudoku (updateGrid grid (row, col) num)
                        Just _ -> acc) Nothing possibleNums

-- 打印数独
printGrid :: Grid -> IO ()
printGrid grid =
  V.mapM_ (\row -> do
           V.mapM_ (
um -> putStr $ show num ++ " ") row
           putStrLn "") grid

-- 使用例子
main :: IO ()
main = do
  let grid = V.fromList [ V.fromList [5, 3, 0, 0, 7, 0, 0, 0, 0]
                        , V.fromList [6, 0, 0, 1, 9, 5, 0, 0, 0]
                        , V.fromList [0, 9, 8, 0, 0, 0, 0, 6, 0]
                        , V.fromList [8, 0, 0, 0, 6, 0, 0, 0, 3]
                        , V.fromList [4, 0, 0, 8, 0, 3, 0, 0, 1]
                        , V.fromList [7, 0, 0, 0, 2, 0, 0, 0, 6]
                        , V.fromList [0, 6, 0, 0, 0, 0, 2, 8, 0]
                        , V.fromList [0, 0, 0, 4, 1, 9, 0, 0, 5]
                        , V.fromList [0, 0, 0, 0, 8, 0, 0, 7, 9]
                        ]
  putStrLn "原始数独:"
  printGrid grid
  putStrLn ""
  case solveSudoku grid of
    Nothing -> putStrLn "无解"
    Just g -> do
      putStrLn "解数独:"
      printGrid g

这个程序中,首先定义了一个Grid类型,表示数独网格。然后定义了isValid函数,用于检查一个数字是否可以填入指定的格子;updateGrid函数,用于在指定的格子中填入一个数字;nextEmpty函数,用于找到下一个空白格子的坐标。

solveSudoku函数是主要的解数独函数,它首先检查是否还有空白格子,如果没有则返回成功的解,如果有,则尝试在当前空白格子中填入1-9的数字,然后递归调用自身继续解数独。

printGrid函数用于打印数独,按照行列格式将数独网格打印出来。

main函数中,定义了一个初始的数独网格,并调用solveSudoku函数解题,最后打印出结果。

使用上述程序解题的输出如下:

原始数独:
5 3 0 0 7 0 0 0 0 
6 0 0 1 9 5 0 0 0 
0 9 8 0 0 0 0 6 0 
8 0 0 0 6 0 0 0 3 
4 0 0 8 0 3 0 0 1 
7 0 0 0 2 0 0 0 6 
0 6 0 0 0 0 2 8 0 
0 0 0 4 1 9 0 0 5 
0 0 0 0 8 0 0 7 9 

解数独:
5 3 4 6 7 8 9 1 2 
6 7 2 1 9 5 3 4 8 
1 9 8 3 4 2 5 6 7 
8 5 9 7 6 1 4 2 3 
4 2 6 8 5 3 7 9 1 
7 1 3 9 2 4 8 5 6 
9 6 1 5 3 7 2 8 4 
2 8 7 4 1 9 6 3 5 
3 4 5 2 8 6 1 7 9

可以看到,程序成功解出了数独。