【发布时间】:2017-02-06 22:43:37
【问题描述】:
我在 Prolog 中实现了以下电源程序:
puissance(_,0,1).
puissance(X,N,P) :- N>0,A is N-1, puissance(X,A,Z), P is Z*X.
代码做了应该做的事情,但是在正确答案之后它会打印“假”。我不明白为什么。我正在使用 swi-prolog。
【问题讨论】:
-
这表示没有进一步的解决方案。
我在 Prolog 中实现了以下电源程序:
puissance(_,0,1).
puissance(X,N,P) :- N>0,A is N-1, puissance(X,A,Z), P is Z*X.
代码做了应该做的事情,但是在正确答案之后它会打印“假”。我不明白为什么。我正在使用 swi-prolog。
【问题讨论】:
可以这样做:
puissance(X,N,P) :-
( N > 0 ->
A is N-1,
puissance(X,A,Z),
P is Z*X
; P = 1 ).
然后它只会打印一个答案。
(您的代码在每次递归调用时都会留下一个“选择点”,因为您有两个析取项且没有剪切。在某处使用 if-then-else 或剪切会删除这些。然后取决于解释器会发生什么。Sicstus 仍然询问您是否想要((试图找到))更多答案。)
【讨论】:
目前,puissance/3 有 3 个不同版本,我想展示其中一些之间的显着语义差异。
作为一个测试用例,我考虑查询:
?- puissance(X, Y, Z), false。这个查询是什么意思?声明式地,它显然等同于 false。不过,这个查询非常有趣,因为它终止了 iff puissance/3 普遍终止。
现在,让我们尝试对程序的不同变体进行查询:
原始定义(来自问题):
?- puissance(X, Y, Z), false.
ERROR: puissance/3: Arguments are not sufficiently instantiated
接受的答案:
?- puissance(X, Y, Z), false.
false.
其他答案:
?- puissance(X, Y, Z), false.
ERROR: puissance/3: Arguments are not sufficiently instantiated
显然,接受答案中显示的解决方案会产生不同的结果,值得进一步考虑。
程序又来了:
puissance(_,0,1) :- !. puissance(X,N,P) :- N>0,A 是 N-1, puissance(X,A,Z), P 是 Z*X。让我们先问一个简单的问题:有哪些解决方案?这被称为最通用查询,因为它的参数都是新鲜的变量:
?- puissance(X,Y,Z)。 Y = 0, Z = 1。程序回答:只有单一解决方案:Y=0, Z=1。
这是不正确(要查看此内容,请尝试查询 ?- puissance(0, 1, _) which succeeds,与声称 Y 的 same 程序相反只能是0),和问题中显示的程序有很大区别。为了比较,原始程序产生:
没关系:在回溯时,程序会抛出实例化错误,表明此时无法进行进一步推理。但至关重要的是,它确实不只是失败了!
所以,让我们坚持原来的程序,并考虑查询:
?- puissance(1, 1, Z)。 Z = 1 ; 错误。我们想摆脱false,这是因为程序不确定。
解决此问题的一种方法是使用来自library(clpfd) 的zcompare/3。这可以让您具体化比较,并使结果可用于索引,同时保留谓词的一般性。
这是一种可能的解决方案:
puissance(X, N, P) :- zcompare(C, 0, N), puissance_(C, X, N, P)。 puissance_(=, _, 0, 1)。 puissance_(有了这个版本,我们得到:
?- puissance(1, 1, Z)。 Z = 1。这现在是确定性,正如预期的那样。
现在,让我们考虑上面这个版本的测试用例:
?- puissance(X, Y, Z), false。 不终止啊哈!因此,此查询既不会引发实例化错误,也不会终止,因此与迄今为止发布的所有版本都不同。
让我们考虑一下这个程序的最通用查询:
?- puissance(X,Y,Z)。 Y = 0, Z = 1 ; X = Z, Y = 1, Z inf..sup ; Y = 2, X^2#=Z, Z in 0..sup ; Y = 3, _G3136*X#=Z, X^2#=_G3136, _G3136 在 0..sup ; 等等啊哈!所以我们得到了满足这个关系的所有整数的符号表示。
这很酷,因此我建议您在 Prolog 中对整数进行推理时使用 CLP(FD) 约束。这将使您的程序更通用,也让您更轻松地提高其效率。
【讨论】:
您可以在解决方案中添加 cut 运算符(即!),这意味着在第一次成功统一达到该点之后,prolog 不应尝试回溯并找到更多解决方案。 (即您正在修剪解决方案树)。
puissance(_,0,1) :- !.
puissance(X,N,P) :- N>0,A is N-1, puissance(X,A,Z), P is Z*X.
外行的解释:
prolog 尝试查看是否有更多解决方案的原因是:
在递归中最后一次调用 puissance 时,first puissance 子句自 P=1 以来成功,并且您一路返回顶部调用以执行与 P 的统一以及该选择产生的最终价值。
然而,对于puissance 的最后一次调用,Prolog 没有机会检查 second puissance 子句是否也可以满足并且可能导致不同的解决方案,因此除非您告诉它不要检查进一步的解决方案(通过在第一个子句成功后使用剪切),它有义务回到那个点,并检查第二个子句。
一旦这样做,它就会发现第二个子句不能满足,因为 N = 0,因此该特定尝试失败。
所以“假”实际上意味着 prolog 也检查了其他选择点,并且无法以任何其他方式统一 P 以满足它们,即没有更多有效的统一 P。
而事实上,您首先可以选择寻找其他解决方案,这恰恰意味着还有其他路线,其中可能存在尚未探索的可满足子句。
【讨论】:
-> 条件运算符is defined using cut under the hood。 (即,它更多的是需要注意的副作用,而不是人们可能期望“if then else”构造在 prolog 的上下文中应该做的事情)