我在Haskell写了一个数独求解器。它通过一个列表,当它找到'0'(一个空单元格)时,它将获得适合的数字并尝试它们:
import Data.List (group, (\\), sort)
import Data.Maybe (fromMaybe)
row :: Int -> [Int] -> [Int]
row y grid = foldl (\acc x -> (grid !! x):acc) [] [y*9 .. y*9+8]
where y' = y*9
column :: Int -> [Int] -> [Int]
column x grid = foldl (\acc n -> (grid !! n):acc) [] [x,x+9..80]
box :: Int -> Int -> [Int] -> [Int]
box x y grid = foldl (\acc n -> (grid !! n):acc) [] [x+y*9*3+y' | y' <- [0,9,18], x <- [x'..x'+2]]
where x' = x*3
isValid :: [Int] -> Bool
isValid grid = and [isValidRow, isValidCol, isValidBox]
where isValidRow = isValidDiv row
isValidCol = isValidDiv column
isValidBox = and $ foldl (\acc (x,y) -> isValidList (box x y grid):acc) [] [(x,y) | x <- [0..2], y <- [0..2]]
isValidDiv f = and $ foldl (\acc x -> isValidList (f x grid):acc) [] [0..8]
isValidList = all (\x -> length x <= 1) . tail . group . sort -- tail removes entries that are '0'
isComplete :: [Int] -> Bool
isComplete grid = length (filter (== 0) grid) == 0
solve :: Maybe [Int] -> Maybe [Int]
solve grid' = foldl f Nothing [0..80]
where grid = fromMaybe [] grid'
f acc x
| isValid grid = if isComplete grid then grid' else f' acc x
| otherwise = acc
f' acc x
| (grid !! x) == 0 = case guess x grid of
Nothing -> acc
Just x -> Just x
| otherwise = acc
guess :: Int -> [Int] -> Maybe [Int]
guess x grid
| length valid /= 0 = foldl f Nothing valid
| otherwise = Nothing
where valid = [1..9] \\ (row rowN grid ++ column colN grid ++ box (fst boxN) (snd boxN) grid) -- remove numbers already used in row/collumn/box
rowN = x `div` 9 -- e.g. 0/9=0 75/9=8
colN = x - (rowN * 9) -- e.g. 0-0=0 75-72=3
boxN = (colN `div` 3, rowN `div` 3)
before x = take x grid
after x = drop (x+1) grid
f acc y = case solve $ Just $ before x ++ [y] ++ after x of
Nothing -> acc
Just x -> Just x
对于一些谜题,这是有效的,例如这个:
sudoku :: [Int]
sudoku = [5,3,0,6,7,8,0,1,2,
6,7,0,0,0,0,3,4,8,
0,0,8,0,0,0,5,0,7,
8,0,0,0,0,1,0,0,3,
4,2,6,0,0,3,7,9,0,
7,0,0,9,0,0,0,5,0,
9,0,0,5,0,7,0,0,0,
2,8,7,4,1,9,6,0,5,
3,0,0,2,8,0,1,0,0]
不到一秒,不过这一次:
sudoku :: [Int]
sudoku = [5,3,0,0,7,0,0,1,2,
6,7,0,0,0,0,3,4,8,
0,0,0,0,0,0,5,0,7,
8,0,0,0,0,1,0,0,3,
4,2,6,0,0,3,7,9,0,
7,0,0,9,0,0,0,5,0,
9,0,0,5,0,7,0,0,0,
2,8,7,4,1,9,6,0,5,
3,0,0,2,8,0,1,0,0]
我还没有看完。我不认为这是方法的问题,因为它确实返回了正确的结果。
剖析显示大部分时间都花在“isValid”函数上。这个功能有什么明显低效/慢的东西吗?
答案 0 :(得分:6)
实施当然是可以改进的,但这不是问题。问题是对于第二个网格,简单的猜测和检查算法需要很多的回溯。即使你将每个函数加速1000倍,也会有一些网格,它仍然需要宇宙年龄的几倍才能找到(首先,如果网格不是唯一的)解决方案。
你需要一个更好的算法来避免这种情况。避免此类情况的一种相当有效的方法是首先猜测具有最少可能性的平方。这并不能避免所有不良情况,但会减少很多。
您还应该做的一件事是将length thing == 0
支票替换为null thing
。由于此处出现相对较短的列表,效果有限,但一般来说它可能很戏剧性(通常您也不应使用length list <= 1
,而是使用null $ drop 1 list
)。
答案 1 :(得分:1)
isValidList = all (\x -> length x <= 1) . tail . group . sort -- tail removes entries that are '0'
如果原始列表不包含任何零,tail
将删除其他内容,可能是两个列表。我将tail . group. sort
替换为group . sort . filter (/= 0)
。
我不明白为什么isValidBox
和isValidDiv
使用foldl
因为map
似乎已经足够了。我错过了什么/他们做得非常聪明吗?