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

时间:2018-02-10 16:39:28

标签: prolog

我在Prolog中玩弄递归,我很困惑。我正在尝试编写可以确定数字是偶数还是奇数的规则。我知道还有其他stackoverflow问题,但我不关心有一个有效的解决方案,我更想知道为什么我的工作不起作用。

以下是我的规则:

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吗?

4 个答案:

答案 0 :(得分:3)

当您查询even(0)时,它会成功,如您所见。但是你也看到它会提示你获得更多的结果,因为它留下了choicepoint,这是逻辑中的一个地方,Prolog决定它可以回来并探索其他可能成功查询的替代方案。回到选择点并试图找到更多的解决方案时,它找不到更多,所以它会回来" false"因为它找不到更多解决方案。所以它确实找到了一个解决方案,但选择点引起了回溯,之后没有找到其他解决方案。您的其他成功查询也是如此。

您需要注意的是,如果您进行更一般的查询,则会出错(例如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))在找到第一个解决方案后终止。

答案 1 :(得分:1)

您拥有的返回值是正确的。重点是Prolog如何评估谓词。当你查询即

even(2)

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

要查看引擎盖下的确切操作,请访问: https://swish.swi-prolog.org

在左侧类型规则(即奇数/偶数)和查询窗口类型,如&#39; odd(2)&#39;,但在运行点击&#39;解决方案&#39; - &gt ;&#39;调试(痕量)&#39 ;.它将让你逐步了解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))等。

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

答案 2 :(得分:0)

Prolog所做的是“深度优先搜索”,这意味着Prolog会遍历决策树,直到它找到解决方案并成功或者失败。在任何一种情况下,一个称为“回溯”的过程就会开始。在此过程中,经过选择树,Prolog会跟踪它有多条可能的路线,这些路线可能会满足目标。决策树中的这一点称为“选择点”。

这意味着Prolog将

  1. 搜索 - &gt;
  2. 成功或失败 - &gt;
  3. 回到最后一个选择点 - &gt;
  4. 重复,直到尝试了所有可能的路径
  5. 鉴于你的计划:

    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可能会发现它是一个无限递归的谓词,即使它是一个有效的声明路径,也不会尝试满足它,但它不是一个有效的程序路径。

答案 3 :(得分:0)

如果你知道模式是(+),你可以放置一个剪切, 抑制不必要的选择点:

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

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

?- even(3).
false.

?- even(4).
true.

但是如果模式不是固定的,你要多加小心 与削减。大概写一个单独精心制作的 每种模式的谓词。

CLP(FD) 本身似乎没有帮助,它无法避免需要 进行切割,但有时可以避免编码的需要 不同模式的不同变体。