【问题标题】:Prolog - confused about return results of recursive ruleProlog - 对递归规则的返回结果感到困惑
【发布时间】:2020-12-23 22:50:45
【问题描述】:

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

【问题讨论】:

    标签: prolog


    【解决方案1】:

    当你查询even(0)时,如你所见,它成功了。但是您也看到它提示您获取更多结果,因为它留下了choicepoint,这是逻辑中 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)) 在找到第一个解决方案后终止。

    【讨论】:

    • 如何编辑规则以避免选择点?此外,我尝试使用 CLP 运算符复制粘贴您的简介,但在“#”字符上出现错误。我正在swish.swi-prolog.org 上测试所有内容(不确定这是否会有所不同)。
    • @EthanMurad 如果您使用的是 SWI Prolog,则需要有 :- use_module(library(clpfd)). 为避免选择点,您需要重构代码,因为此解决方案虽然简单透明,但具有关于它的方面。选择点有问题吗?
    • 这不完全是一个问题,只是问一个数字是否是偶数并得到多个答案是不自然的。你能举一个没有这个问题的简单重构代码的例子吗?
    • @smackattack 我同意,确实如此。
    • @smackattack,是的,当然,如果您知道最多只有一个解决方案,或者您肯定只关心第一个可能的解决方案,那么once/1 会为您处理此案。我会在答案中添加更多内容。
    【解决方案2】:

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

    even(2)
    

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

    要检查引擎盖下的具体执行情况,请访问: https://swish.swi-prolog.org

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

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

    从上面的链接中,尝试这样的代码作为一个反向示例:

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

    现在第一次评估 numeric(0) 返回 succ(0),另一次返回 succ(succ(0)) 等等。

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

    【讨论】:

      【解决方案3】:

      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 特定版本可能认为它是一个无限递归谓词,即使它是有效的声明路径,但不是有效的过程路径,甚至都不会尝试满足它。

      【讨论】:

        【解决方案4】:

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

        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) 本身似乎没有帮助,它无法避免需要 进行切割,但有时可以避免编码的需要 不同模式的不同变体。

        【讨论】:

          猜你喜欢
          • 2018-11-27
          • 2014-07-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-24
          • 2017-06-12
          • 2013-05-15
          • 1970-01-01
          相关资源
          最近更新 更多