Prolog - 对递归规则的返回结果感到困惑



我在Prolog中玩递归,我很困惑。我正在尝试编写可以确定数字是偶数还是奇数的规则。我知道还有其他关于这个问题的堆栈溢出问题,但我不在乎有一个有效的解决方案,我更感兴趣的是知道为什么我的不起作用。

以下是我的规则:

even(0).
even(N) :- N>0, N1 is N-1, odd(N1).
odd(N) :- N>0, N1 is N-1, even(N1).

当我查询even(0)时,我得到 2 个结果。第一个结果是真的,第二个结果是假的。这也发生在odd(1)even(2)odd(3)等。为什么我得到 2 个返回结果?我不应该只得到 1 吗?

当您查询even(0)时,如您所见,它成功了。但你也看到它提示你输入更多结果,因为它留下了一个选择点,这是 Prolog 决定它可以回来探索其他替代方案以获得潜在成功查询的逻辑中的一个位置。回到选择点并尝试找到更多解决方案时,它没有找到更多,所以它返回"错误",因为它没有找到更多的解决方案。所以它确实只找到了一个解决方案,但选择点导致回溯,之后它没有找到其他解决方案。其他成功查询也是如此。

你会注意到,如果你进行一个更通用的查询,它会给出一个错误(示例取自GNU Prolog):

| ?- even(N).
N = 0 ? ;
uncaught exception: error(instantiation_error,(>)/2)
| ?-

这是因为您使用的是特定的算术表达式运算符,这些运算符需要实例化变量。这些是关系运算符,如(>)/2is/2运算符。您可以使用 CLP(FD) 运算符使解决方案更具关系性,这些运算符专为使用整数进行推理而设计:

even(0).
even(N) :-
N #> 0,
N1 #= N-1,
odd(N1).
odd(N) :-
N #> 0,
N1 #= N-1,
even(N1).

然后你会得到一个更通用的解决方案,它更完整,更有用:

| ?- even(N).
N = 0 ? ;
N = 2 ? ;
N = 4 ? ;
N = 6 ? ;
...
| ?- odd(N).
N = 1 ? ;
N = 3 ? ;
N = 5 ? ;
N = 7 ?
...


如果你知道最多有一个答案,或者如果你只关心第一个可能的答案,你可以使用once/1(例子取自SWI Prolog):
2 ?- even(2).
true ;
false.
3 ?- once(even(2)).
true.
4 ?- even(N).
N = 0 ;
N = 2 ;
N = 4 ;
...
5 ?- once(even(N)).
N = 0.
6 ?-

正如预期的那样,once(even(N))在找到第一个解决方案后终止。

您拥有的返回值是正确的。关键是Prolog如何评估谓词。当您查询时,即

even(2)

Prolog首先评估这个谓词是/真。当经历下一个可能性时,它返回 No/false,因为它找不到更多。

要检查引擎盖下到底执行了什么,请访问: https://swish.swi-prolog.org

在左侧键入规则(即奇数/偶数)和查询窗口类型,如"Odd(2)",但在运行之前单击"解决方案"->"调试(跟踪)"。它会让你一步一步地了解Prolog正在做的事情。

另外,请查看下面教程中的后续示例。 http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlse9

从上面的链接中,尝试以下代码以获取反向示例:

numeral(0). 
numeral(succ(X))  :-  numeral(X).

现在第一次计算数字(0)返回succ(0),另一个时间succ(succ(0))等。

每次下一次评估都会为查询带来另一种可能的解决方案。

Prolog所做的是"深度优先搜索",这意味着Prolog会遍历决策树,直到找到解决方案并成功或失败。无论哪种情况,都会启动一个称为"回溯"的过程。在此过程中,通过选择树,Prolog跟踪它有多条可能满足目标的可能路线。决策树中的此类点称为"选择点"。

这意味着Prolog将

  1. 搜索 ->
  2. 成功或失败 ->
  3. 回到最后的选择点->
  4. 重复此步骤,直到尝试了所有可能的路径

给定您的程序:

even(0).
even(N) :- N>0, N1 is N-1, odd(N1).
odd(N) :- N>0, N1 is N-1, even(N1).

我们可以清楚地看到两种满足even(0).的方法。第一个是事实even(0),第二个是递归规则even(N)。Prolog 从上到下、从左到右读取,因此第一次遇到是even(0).这是真的,第二次是even(N).,它通过 N-1 使结果N1 = -1,然后通过odd(N)使结果N1 = -2,这不等于even(0).,所以它失败,然后再次调用even(N)。您的Prolog的特定版本可能会看到它是一个无限递归谓词,甚至不会尝试满足它,即使它是一个有效的声明路径,但不是有效的过程路径。

如果你知道模式是(+),你可以放置一个切口, 要抑制不必要的选择点,请执行以下操作:

even(0) :- !.
even(N) :- N > 0, N1 is N-1, odd(N1).
odd(N) :- N > 0, N1 is N-1, even(N1).

以上比用 包装查询要好 一次/1,因为它允许Prolog解释器 使用上次调用优化。现在没有了 额外选择点的问题:

?- even(3).
false.
?- even(4).
true.

但如果模式不固定,就要多加小心 有切口。可能写一个单独的精心制作的 每个模式的谓词。

CLP(FD)本身似乎无济于事,它无法避免需要 放置切割,但有时可以避免编码的需要 不同模式的不同变体。

最新更新