我查看了手册和文档,但仍然不明白。我正在尝试实现一个数独解决方案,在写出游戏的所有其他规则后,我根据老师的指示添加了标签(Board)。
然而,我仍然没有得到它的工作原理或它正在做什么。不应该有其他限制(我有检查说数字必须是1..9,行必须全部不同等等)给我答案吗?
答案 0 :(得分:10)
如果你想快速学习Prolog和CLP(FD),可以使用Prolog的顶级shell来玩,直到你熟悉它为止。事实上,您需要了解有关CLP(FD)和Prolog的所有信息;或差不多。不需要写(他们的名字是什么?)文件,一切都符合一条线。是的,我知道,我们的父母警告我们:我的孩子,答应我,永远不要做一个班轮。但是你会学得更快。
那么你有?-
等待吗?
在传统的Prolog(没有约束)中,您从查询中获得的是所谓的答案替换。在许多情况下,这种答案替代已经描述了一种解决方案如果对于每个变量,则找到变量空闲项。让我们看一个具体的例子并描述一个包含5个元素的列表,其中每个元素都是1到5之间的值。在这种情况下,找到L
的不同值的解决方案。
?- N = 5, length(L,N),maplist(between(1,N),L).
N = 5, L = [1, 1, 1, 1, 1] ;
N = 5, L = [1, 1, 1, 1, 2] ;
N = 5, L = [1, 1, 1, 1, 3] ...
Prolog只会向您展示一种解决方案(暗中希望您会对它感到满意,它有点懒,不严格)。你得到所有人输入 SPACE 或; 。尝试一下,看看它们有多少......
总共有5^5
个解决方案。如果您只想从众多解决方案中挑选一些解决方案,那就不太实际了。以这种方式表示大量解决方案非常无效。然后,想想无限集! Prolog或任何有限的存在如何枚举无限集?我们只能开始这样做,因为我们是有限的。
为了克服这个问题,Prolog并不总是被迫展示具体的价值观,即解决方案,但可以通过显示答案来将它们包含在内:
?- N = 5, length(L,N).
N = 5, L = [_A, _B, _C, _D, _E].
此答案(-substitution)包含上述所有5^5
个答案,还有更多答案,例如L = [stack,over,flow,dot,com]
。事实上,它描述了一组无限的解决方案!我不是说我们有限的生物不能这样做吗?只要我们坚持具体的解决方案,我们就不能,但如果我们对答案感到满意,我们就可以做到不可能。
这个想法可以扩展到描述更具体的集合。所有人都有一个答案。对于有关整数的集合,我们有library(clpfd)
。像这样使用它:
?- use_module(library(clpfd)).
?- asserta(clpfd:full_answer). % only necessary for SICStus
我们现在可以重申我们的原始查询(在SWI中,您可以执行 Cursor up↑来获取它):
?- N = 5, length(L,N),L ins 1..N.
N = 5, L = [_A, _B, _C, _D, _E],
_A in 1..5, _B in 1..5, _C in 1..5, _D in 1..5,_E in 1..5.
现在所有3125解决方案都只用一个答案进行了简洁描述。 (3125?那是5^5
)。我们可以继续说明进一步的要求,例如它们都是不同的:
?- N = 5, length(L,N),L ins 1..N, all_different(L).
N = 5, L = [_A, _B, _C, _D, _E],
_A in 1..5,_B in 1..5,_C in 1..5,_D in 1..5,_E in 1..5,
all_different([_A, _B, _C, _D, _E]).
所有约束的共同点(实际上)是它们没有列举解决方案,而是试图保持一致性。让我们试试这个,说明第一个元素应该是1:
?- N = 5, length(L,N),L ins 1..N, all_different(L), L = [1|_].
N = 5, L = [1, _A, _B, _C, _D],
_A in 2..5,_B in 2..5,_C in 2..5,_D in 2..5,
all_different([1, _A, _B, _C, _D]).
你看到效果了吗?他们迅速改变了他们的域名!现在他们全都在2..5
。
他们都应该在1..4
:
?- N = 5, length(L,N),L ins 1..N, all_different(L), L = [1|_], L ins 1..4.
N = 5, L = [1, _A, _B, _C, _D],
_A in 2..4,_B in 2..4,_C in 2..4,_D in 2..4,
all_different([1, _A, _B, _C, _D]).
同样,它们会更新。但是......想一想:剩下4个变量,它们应该都是不同的,但它们只有3个不同的值。
所以我们发现Prolog有点太懒了。实际上有一个名为all_distinct/1
的更好的约束现在会失败,但无论系统有多少聪明的约束,都会有总是这种不一致。问Professor Gödel。拯救的唯一方法是错误或无限循环。
所以我们需要另一种方法来确保答案确实描述了真正的解决方案。输入标签!使用label/1
或labeling/2
,我们可以消除所有这些奇怪的约束并破坏不一致性:
?- N = 5, length(L,N),L ins 1..N, all_different(L), L = [1|_], L ins 1..4, labeling([], L).
false.
?- N = 5, length(L,N),L ins 1..N, all_different(L), L = [1|_], labeling([], L).
N = 5, L = [1, 2, 3, 4, 5] ;
N = 5, L = [1, 2, 3, 5, 4] ;
N = 5, L = [1, 2, 4, 3, 5] ...
我们怎样才能确定这些是真正的解决方案?容易:除了答案替换 1 之外,它们不包含任何额外的目标。因为我们忘了一些:
?- N = 5, length(L,N),L ins 1..N, all_different(L), L = [1,B,C|_], labeling([],[B,C]).
N = 5, L = [1, 2, 3, _A, _B], B = 2, C = 3,
_A in 4..5, _B in 4..5,
all_different([1, 2, 3, _A, _B]),
他们会表现出来。
SWI的labeling/2
有一个非常有用的保证:
标签始终完整,始终终止,并且不会产生冗余解决方案。
1由于SWI toplevel未显示所有约束,因此您需要在其周围包裹call_residue_vars(Goal, Vs)
。但对于简单的顶级查询,上面就足够了。
答案 1 :(得分:2)
粗略地说,约束编程有两个阶段:约束传播和搜索。
单独的约束传播可以给出具体的值,而且经常会这样。但一般来说,它只会将域(变量的可能值集)减少为较小的子集,然后需要搜索(来自子集的标签值,通过约束传播获得)。
非常简单的例子(伪代码):
A #:: 0..1,
B #:: 0..1,
A #\= B
约束传播本身无法解决这个问题,它甚至无法减少A或B的域 - 它只能创建一个延迟约束。之后,搜索(标签)为A尝试值0,延迟约束激活,B域减少到{1}。
相比之下,这可以通过单独的约束传播来解决:
A #:: 1,
B #:: 0..1,
A #\= B