prolog数独求解器耗尽全局堆栈

时间:2013-12-09 22:20:21

标签: prolog swi-prolog sudoku clpfd

我尝试在swi-prolog中编写二进制数独求解器。 (二进制数独解释here

问题是我现在正在耗尽全局堆栈。我给它2 gb应该绰绰有余。我使用的是有缺陷的算法吗?有没有什么我可以做得更好,以避免因为这些小谜题而缺乏全局堆栈错误?

更多信息:我已经在4X4谜题上耗尽了堆栈,其中第一个约束只应用了6 ^ 4种可能性。 您可以使用以下方法查询此问题:

problems(2,Field),binary_sudoku(Field).

代码在这里:

:-use_module(library(clpfd)).

valid_row(Row) :-
    Row ins 0..1,
    length(Row,L),
    sum(Row,#=,L/2).

matrixNth1(Matr,X,Y,El) :-
    nth1(Y,Matr,CurRow),
    nth1(X,CurRow,El).

all_diff([]).
all_diff([X|Y]) :-
    maplist(dif(X),Y),
    all_diff(Y).


valid(_,1,1).
valid(Rows,1,Y) :-
    length(Rows,Y).
valid(Rows,X,1) :-
    length(Rows,X).
valid(Rows,X,X) :-
    length(Rows,X).

valid(Rows,X,Y) :-
    matrixNth1(Rows,X,Y,0).
valid(Rows,X,Y):-
    AboveY is Y-1,
    matrixNth1(Rows,X,AboveY,0).
valid(Rows,X,Y):-
    BelowY is Y+1,
    matrixNth1(Rows,X,BelowY,0).
valid(Rows,X,Y):-
    LeftX is X-1,
    matrixNth1(Rows,LeftX,Y,0).
valid(Rows,X,Y):-
    RightX is X+1,
    matrixNth1(Rows,RightX,Y,0).

binary_sudoku(Rows) :-
    length(Rows,Height),
    transpose(Rows,Cols),
    length(Cols,Height),
    maplist(valid_row,Rows),
    foreach(between(1,Height,X),foreach(between(1,Height,Y),valid(Rows,X,Y))),
    all_diff(Rows),all_diff(Cols).


problems(1,[[_,_],[_,_]]).

problems(2,[[_,_,_,_],[_,_,_,_],[_,_,_,_],[_,_,_,_]]).

2 个答案:

答案 0 :(得分:5)

这是ECLiPSe中的紧凑型解决方案(带有约束和建模扩展的Prolog,http://eclipseclp.org)。它对每行/每列的0 / 1s数使用sum-constraints,对no-three-1s条件使用sequence / 4约束,使用lex_ne / 2来强制行之间的差异。解决方案搜索由最后的标记/ 1调用完成。此外,使用Matrix-notation,这比这些设置中的列表更方便。

:- lib(gfd).

solve(Name, Mat) :-
    problem(Name, Mat),
    dim(Mat, [N,N]),
    Mat #:: 0..1,
    N #= 2*K,
    ( for(I,1,N), param(Mat,K,N) do
        sum(Mat[I,1..N]) #= K,
        sum(Mat[1..N,I]) #= K,
        sequence(1, 2, 3, Mat[I,1..N]),
        sequence(1, 2, 3, Mat[1..N,I]),
        ( for(J,I+1,N), param(Mat,I,N) do
            lex_ne(Mat[I,1..N], Mat[J,1..N]),
            lex_ne(Mat[1..N,I], Mat[1..N,J])
        )
    ),
    labeling(Mat).

problem(2, [](
    [](_,1,0,_,_,_,_,0,_,_,0,_),
    [](_,1,1,_,_,1,_,_,_,_,_,_),
    [](_,_,_,_,_,_,_,_,1,_,_,0),
    [](_,_,0,0,_,_,_,_,_,_,_,0),
    [](_,_,_,_,_,_,1,1,_,0,_,_),
    [](_,1,_,0,_,1,1,_,_,_,1,_),
    [](_,_,_,_,_,_,_,_,1,_,_,_),
    [](1,_,_,1,_,_,_,_,_,_,0,_),
    [](_,1,_,_,_,_,_,_,0,_,_,_),
    [](_,_,_,_,_,_,_,0,_,_,_,_),
    [](1,_,_,_,_,_,_,_,_,_,_,1),
    [](_,1,_,1,_,_,_,_,_,0,0,_))).

这快速提出了(独特的)解决方案:

?- solve(2, M).
M = []([](1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0),
       [](0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1),
       [](1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0),
       [](1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0),
       [](0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1),
       [](0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0),
       [](1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1),
       [](1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1),
       [](0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0),
       [](0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0),
       [](1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1),
       [](0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1))
Yes (0.03s cpu, solution 1, maybe more)

答案 1 :(得分:3)

您的代码存在一些问题。如果你真的是一个Prolog / clpfd初学者,你可以考虑从容易开始的事情开始。

CLP程序的想法是首先设置约束(确定性),然后搜索解决方案(这是不确定的)。在你的代码中,valid_row / 1和all_diff / 1可以被认为是约束,但是有效/ 3是不确定的,并且有很多选择。

因此,您应该做的第一个更改是将您的呼叫转移到有效的/ 3到最后。

然后你应该改变有效的/ 3,以便它系统地枚举所有可能的变量赋值。使用您当前的代码和3x3矩阵,前几个解决方案

foreach(between(1,Height,X),foreach(between(1,Height,Y),valid(Rows,X,Y)))

生成

[[A, 0, C], [0, 0, 0], [G, 0, I]]
[[A, 0, C], [0, 0, 0], [G, 0, 0]]
[[A, 0, C], [0, 0, 0], [G, 0, I]]
[[A, 0, C], [0, 0, 0], [G, 0, I]]
[[A, 0, 0], [0, 0, F], [G, 0, I]]
...

这很糟糕,因为它们仍然包含变量,它们甚至不是互斥的。这两个意味着您一遍又一遍地访问相同的作业。重写它使得每次成功时,所有变量都设置为0/1,并且所有解决方案都不同。然后,您将确保只遍历一次搜索空间,并且您可能有机会在合理的时间内找到解决方案。