我是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]],
[<>>><>,<<<>><,<<<><>,<><<><,>>><><,><>><>,<>>><>,<>><><,><<>>>],
[<<<><>,><<>>>,<><<><,><<<>>,><><<<,<><><>,<>>>><,><><><,<>><>>])).
并且这个一直在一夜之间运行而没有得出任何结论,在这一点上,我不知道出了什么问题。我期待一些扩展问题,但不是这个比例!
如果真正了解他们正在做什么的人可以为此发光,那就太棒了!谢谢!
答案 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/3
:maplist(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中所述。