Prolog - jigoku solver - 运行时间

时间:2015-07-19 12:44:03

标签: prolog runtime sudoku clpfd

我是Prolog的新手(例如:我在7周内只用7种语言完成了Prolog章节),因此非常欢迎对以下任何代码的一般性评论。

首先:什么是jigoku?它就像一个数独,除了你得到一个空网格,并且在每个3x3块中,给出了相邻时隙之间的不等式。示例:http://krazydad.com/jigoku/books/KD_Jigoku_CH_8_v18.pdf。您仍然需要填充网格,以便每行,每列和每个块都包含数字1-9。

我已尝试根据此数独求解器实现求解器:http://programmablelife.blogspot.co.uk/2012/07/prolog-sudoku-solver-explained.html。出于调试原因,我开始使用一个非常有效的4x4示例:

:- use_module(library(clpfd)).

small_jidoku(Rows, RowIneqs, ColIneqs) :-
  Rows = [A,B,C,D], 
  append(Rows, Vs), Vs ins 1..4,
  maplist(all_distinct, Rows),
  transpose(Rows, Columns),     
  maplist(all_distinct, Columns),
  blocks(A, B), blocks(C,D), 
  maplist(label, Rows),
  fake_check_ineqs(Rows, RowIneqs),
  fake_check_ineqs(Columns, ColIneqs),
  pretty_print([A,B,C,D]).      

blocks([], []).       
blocks([A,B|Bs1], [D,E|Bs2]) :-     
  all_distinct([A,B,D,E]),      
  blocks(Bs1, Bs2).

fake_check_ineqs([],[]).
fake_check_ineqs([Head|Tail], [Ineq1|TailIneqs]) :- 
    Head = [A,B,C,D],
    atom_chars(Ineq1, [X1,X2]),
    call(X1, A, B),
    call(X2, C, D),
    fake_check_ineqs(Tail, TailIneqs).

pretty_print([]).
pretty_print([Head | Tail]) :-
 print(Head),
 print('\n'),
 pretty_print(Tail).

然后我解决了以下示例:

time(small_jidoku([[A1,A2,A3,A4],[B1,B2,B3,B4],[C1,C2,C3,C4],[D1,D2,D3,D4]],[><,<>,<<,<<],[><,<<,<>,>>])).

这在大约0.5秒的时间内运行。但是,我也尝试用

来解决它
time(small_jidoku([A,B,C,D],[><,<>,<<,<<],[><,<<,<>,>>])).

这似乎需要很长时间。 当我没有指定每行有4个元素时,任何人都可以解释为什么解算器需要更长的时间吗?我的天真答案就是Prolog,如果没有告诉我行的实际格式,也会尝试探索更小/更大的行,从而浪费时间长度为5的行,但这实际上是真的吗?

我的第二个问题是关于9x9版本,这非常类似于4x4,除了块当然更大,并且在检查不等式时还有更多的测试要做。代码如下:

:- use_module(library(clpfd)).

jidoku(Rows, RowIneqs, ColIneqs) :-  
  Rows = [A,B,C,D,E,F,G,H,I],   
  append(Rows, Vs), Vs ins 1..9,
  maplist(all_distinct, Rows),
  transpose(Rows, Columns),     
  maplist(all_distinct, Columns),         
  blocks(A, B, C), blocks(D, E, F), blocks(G, H, I),     
  maplist(label, Rows),
  check_ineqs(Rows, RowIneqs),
  check_ineqs(Columns, ColIneqs),
  pretty_print([A,B,C,D,E,F,G,H,I]).      

blocks([], [], []).       
blocks([A,B,C|Bs1], [D,E,F|Bs2], [G,H,I|Bs3]) :-     
  all_distinct([A,B,C,D,E,F,G,H,I]),      
  blocks(Bs1, Bs2, Bs3).

check_ineqs([],[]).
check_ineqs([Head|Tail], [Ineq1|TailIneqs]) :- 
    Head = [A,B,C,D,E,F,G,H,I],
    atom_chars(Ineq1, [X1, X2, X3, X4, X5, X6]),
    call(X1, A, B),
    call(X2, B, C),
    call(X3, D, E),
    call(X4, E, F),
    call(X5, G, H),
    call(X6, H, I),
    check_ineqs(Tail, TailIneqs).

测试示例:

    time(jidoku([[A1,A2,A3,A4,A5,A6,A7,A8,A9],
        [B1,B2,B3,B4,B5,B6,B7,B8,B9],
        [C1,C2,C3,C4,C5,C6,C7,C8,C9],
        [D1,D2,D3,D4,D5,D6,D7,D8,D9],
        [E1,E2,E3,E4,E5,E6,E7,E8,E9],
        [F1,F2,F3,F4,F5,F6,F7,F8,F9],
        [G1,G2,G3,G4,G5,G6,G7,G8,G9],
        [H1,H2,H3,H4,H5,H6,H7,H8,H9],
        [I1,I2,I3,I4,I5,I6,I7,I8,I9]], 
        [<>>><>,<<<>><,<<<><>,<><<><,>>><><,><>><>,<>>><>,<>><><,><<>>>], 
        [<<<><>,><<>>>,<><<><,><<<>>,><><<<,<><><>,<>>>><,><><><,<>><>>])).

并且这个一直在一夜之间运行而没有得出任何结论,在这一点上,我不知道出了什么问题。我期待一些扩展问题,但不是这个比例!

如果真正了解他们正在做什么的人可以为此发光,那就太棒了!谢谢!

1 个答案:

答案 0 :(得分:3)

以下是我想到的代码版本(其他谓词保持不变):

ineqs(Cells, Ineq) :-
        atom_chars(Ineq, Cs),
        maplist(primitive_declarative, Cs, Ds),
        ineqs_(Ds, Cells).

ineqs_([], _).
ineqs_([Op1,Op2|Ops], [A,B,C|Cells]) :-
        call(Op1, A, B),
        call(Op2, B, C),
        ineqs_(Ops, Cells).

primitive_declarative(<, #<).
primitive_declarative(>, #>).

请注意,调用谓词&#34; check_...&#34;并不是代码正义的一般性,因为谓词表明持有并且可以使用在几个方向:是的,它可用于检查如果约束成立,但它也可以 用于表明约束必须坚持一些变数。因此,我避免使用命令并使用更多声明性名称。

您在ineqs/2中使用jidoku/3maplist(ineqs, Rows, RowsIneqs)等。

您的示例和新版本的结果,使用SWI 7.3.2:

?- length(Rows, 9), maplist(same_length(Rows), Rows),
   time(jidoku(Rows,
   [<>>><>,<<<>><,<<<><>,<><<><,>>><><,><>><>,<>>><>,<>><><,><<>>>],
   [<<<><>,><<>>>,<><<><,><<<>>,><><<<,<><><>,<>>>><,><><><,<>><>>])),
   maplist(writeln, Rows).
% 2,745,471 inferences, 0.426 CPU in 0.432 seconds (99% CPU, 6442046 Lips)
[1,5,4,8,7,2,6,9,3]
[2,3,9,1,6,5,7,4,8]
[6,7,8,3,9,4,2,5,1]
[3,4,1,2,5,6,8,7,9]
[9,6,5,7,1,8,3,2,4]
[8,2,7,9,4,3,1,6,5]
[4,9,3,6,2,1,5,8,7]
[7,8,2,5,3,9,4,1,6]
[5,1,6,4,8,7,9,3,2]
Rows = [[1, 5, 4, 8, 7, 2, 6, 9|...], ...].

事实上,请注意,在这种特定情况下,根本没有标记需要计算唯一解决方案,因为约束求解器足够强大,可以将所有域减少为单例集。

在你以前的版本中,所有的时间都被不必要地浪费在一起,产生最终被认为不一致的排列。使用新版本,约束求解器有机会更早地应用这些知识。

因此,建议首先说明所有约束,然后再调用labeling/2来搜索具体的解决方案,如CLP(FD) manual中所述。