我在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吗?
答案 0 :(得分:3)
当您查询even(0)
时,它会成功,如您所见。但是你也看到它会提示你获得更多的结果,因为它留下了choicepoint,这是逻辑中的一个地方,Prolog决定它可以回来并探索其他可能成功查询的替代方案。回到选择点并试图找到更多的解决方案时,它找不到更多,所以它会回来" false"因为它找不到更多解决方案。所以它确实找到了一个解决方案,但选择点引起了回溯,之后没有找到其他解决方案。您的其他成功查询也是如此。
您需要注意的是,如果您进行更一般的查询,则会出错(例如GNU Prolog):
| ?- even(N).
N = 0 ? ;
uncaught exception: error(instantiation_error,(>)/2)
| ?-
这是因为您正在使用要求实例化变量的特定算术表达式运算符。这些是关系运算符,如(>)/2
和is/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将
鉴于你的计划:
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) 本身似乎没有帮助,它无法避免需要 进行切割,但有时可以避免编码的需要 不同模式的不同变体。