sudoku solver in Haskell

这几天没太多的事做,想着用函数式语言来写点实用的程序,像fib和prime之类的就不想提了(就一行代码的事),写什么程序呢?在网上闲逛时发现sudoku游戏,sudoku十几年前就知道了,学生生涯时也想过用C/Java来实现个智能求解,但到最后往往没写成,主要是用C/Java写的话会很麻烦。

 

现在写程序,本人总是有一种思维惯性,总是想把程序写的更紧凑,更精致,代码行数最少,所以现在解决实际问题时慢慢的不会再用C的思维去看问题了,很多现实中的问题用C去考虑的话会非常复杂,像这个数独,原因是C不具有太强大的抽象能力吧。那OO呢?像Java/Python?很多Java程序员遇到啥问题就直接上面向对象,然后开始疯狂的抽象一大堆类,接着翻开设计模式那本书,从中找几个模式出来,如singleton,factory等,能用上的都用上。OO确实把很多问题模块化了,那模块里面呢?顺着这种思维下去,OO的继承和多态又上来了。结果写出来的程序极为冗长。在这里不是说OO不好,只是不要把OO当做解决一切问题的方法和思维,个人认为OO并不具有强大的抽象能力,像C/++/Java/Python/Ruby等OO语言,本质上还是顺着C的思维走了。

 

说到这里就不能不说FP了,关于FP不想多说,花了几天时间用haskell写了个数独求解程序,可以和C/C++做个比较,如代码行数,可维护性。写这个程序的目的不仅只是好玩,还有就希望用最少的代码行数解决数独,scheme括号太多,C/C++/Java太繁琐,最后决定用haskell来试试,看看haskell的数独版本用了几行代码,结果用了17行(除去空行,board数值定义)

 

 

board = [0,3,4,1,7,0,5,0,0,
         0,6,0,0,0,8,3,0,1,
         7,0,0,3,0,0,0,0,6,
         5,0,0,6,4,0,8,0,7,
         0,1,0,9,0,0,0,6,0,
         3,0,6,0,0,2,0,0,4,
         8,0,0,0,0,9,0,0,2,
         1,0,3,7,0,0,0,9,0,
         0,0,9,0,3,1,7,4,0]



nodup x = (length y) == (length $ nub y) where y = filter (/=0) x
boardlen = round.sqrt.fromIntegral $ length board
pointnum bd answernum = length.last.filter (\x->sum x == answernum) $ inits $ map (\x -> if x /= 0 then 0 else 1) bd

newboard (0:xs) (y:ys) = y:newboard xs ys
newboard (x:xs) s      = x:newboard xs s
newboard s []          = s

pointcheck (row, col) bd = all nodup [map (\x->bd!!(row*9+x)) [0..8], map (\x->bd!!(x*9+col)) [0..8], smallgrid bd] where
                                rowmin = row `div` 3 * 3
                                colmin = col `div` 3 * 3
                                smallgrid bd = [bd !! (row*9+col) | row <- [rowmin..(rowmin+2)], col <- [colmin..(colmin+2)]]

nextanswer answer = filter (pointcheck (row,col) . newboard board) $ map (\x->answer++[x]) [1..boardlen] where
                        point = pointnum board $ length answer
                        (row, col) = divMod point boardlen

sudokuIterate = iterate (concatMap $ nextanswer) [[]]

divide bd = unfoldr (\x -> if x == [] then Nothing else Just (splitAt 9 x)) bd

sudoku = mapM_ (\x -> sequence [putStrLn "answer:", mapM_ print (divide $ newboard board x), putStrLn ""])
                        $ sudokuIterate !! length (filter (== 0) board)

main = sudoku

 

总共17行的haskell代码就搞定了sudoku问题,当然这个版本不是初始版本,是经过了很多次的修改而成的简练版,在网上找了C/C++还有Erlang的版本,代码行数无一少于20行的,那些最少的都有40来行。连Prolog版的往往都20多行。

 

运行结果:

 

*Main> :l algorithm.hs
[1 of 1] Compiling Main             ( algorithm.hs, interpreted )
Ok, modules loaded: Main.
*Main> sudoku         
answer:
[2,3,4,1,7,6,5,8,9]
[9,6,5,4,2,8,3,7,1]
[7,8,1,3,9,5,4,2,6]
[5,9,2,6,4,3,8,1,7]
[4,1,8,9,5,7,2,6,3]
[3,7,6,8,1,2,9,5,4]
[8,4,7,5,6,9,1,3,2]
[1,2,3,7,8,4,6,9,5]
[6,5,9,2,3,1,7,4,8]
 

 

有空还会继续优化上面的程序,看看能否再精简点!(很遗憾,有人竟用了12行haskell代码就解决了)

 

注:用haskell来写sudoku求解除了FP的强大抽象能力外,最主要的就是haskell的lazy特性。

 

 

 

你可能感兴趣的:(sudoku,haskell)