用SMT求解器解数独

July 4, 2014

高中的时候才知道数独,还买了本小册子,至今没填完。

用SMT求解器解数独简直大才小用,而且不用费一点脑力就能用SMT来描述一个数独问题。数独里的每一个格子可以直接对应到SMT里的一个变量,然后根据数独的规则向这些变量加上约束。对于已知的格子,直接让对应变量等于那个值。

完整代码在这

sudoku game = do
    -- 81个变量
    vs :: [SWord8] <- mkExistVars 81
    -- 已知的值
    zipWithM_ setVar game vs
    -- 每个变量  1 <= x <= ⑨
    mapM_ (\x -> constrain $ x .>= 1 &&& x .<= 9) vs
    -- 行/列/九个小方格。这一团地图是神马?(╯°口°)╯(┴—┴)
    mapM_ (mapM_ (allDiff . map (vs !!)))
        [ map (take 9 . iterate (+ 9)) [0 .. 8]
        , map (take 9 . iterate (+ 1)) (take 9 [0, 9 ..])
        , map (concat . take 3 . iterate (map (+ 9)) . take 3 . iterate (+ 1))
            [0,3,6,27,30,33,54,57,60]
        ]
    -- 也不知道是谁规定的,最后都要返回一个SB的true ╮( ̄▽ ̄)╭
    return (true :: SBool)

  where
    setVar 0 _ = return ()
    setVar x v = constrain $ v .== fromIntegral x

    allDiff [] = return ()
    allDiff (x:xs) = mapM_ (constrain . (x ./=)) xs >> allDiff xs

要用1秒左右才能解出来传说中最难的数独,不过不伤脑能有这个速度也算不错了。

这种约束简单而且能直接和SMT变量对应的问题都很好解。

其实更有趣的是生成数独问题。