【问题标题】:Please explain this example of Prolog recursion请解释这个Prolog递归的例子
【发布时间】:2018-02-26 05:54:15
【问题描述】:

我正在学习 Prolog,但我在递归方面遇到了困难。数据库的简单案例我可以理解,但我不能按照这个练习进行,其中实现了redu/2,它将删除给定列表的重复项并将新列表作为第二个参数:

redu([],[]).

redu([H|T], Result):-
  member(H,T),
  redu(T,Result).

redu([H|T], [H|Result]):-
   redu(T, Result).

一条痕迹告诉我:

[trace]  ?- redu([a,b,b,c,a], X).
   Call: (8) redu([a, b, b, c, a], _35630) ? creep
   Call: (9) lists:member(a, [b, b, c, a]) ? creep
   Exit: (9) lists:member(a, [b, b, c, a]) ? creep
   Call: (9) redu([b, b, c, a], _35630) ? creep
   Call: (10) lists:member(b, [b, c, a]) ? creep
   Exit: (10) lists:member(b, [b, c, a]) ? creep
   Call: (10) redu([b, c, a], _35630) ? creep
   Call: (11) lists:member(b, [c, a]) ? creep
   Fail: (11) lists:member(b, [c, a]) ? creep
   Redo: (10) redu([b, c, a], _35630) ? creep
   Call: (11) redu([c, a], _35900) ? creep
   Call: (12) lists:member(c, [a]) ? creep
   Fail: (12) lists:member(c, [a]) ? creep
   Redo: (11) redu([c, a], _35900) ? creep
   Call: (12) redu([a], _35906) ? creep
   Call: (13) lists:member(a, []) ? creep
   Fail: (13) lists:member(a, []) ? creep
   Redo: (12) redu([a], _35906) ? creep
   Call: (13) redu([], _35912) ? creep
   Exit: (13) redu([], []) ? creep
   Exit: (12) redu([a], [a]) ? creep
   Exit: (11) redu([c, a], [c, a]) ? creep
   Exit: (10) redu([b, c, a], [b, c, a]) ? creep
   Exit: (9) redu([b, b, c, a], [b, c, a]) ? creep
   Exit: (8) redu([a, b, b, c, a], [b, c, a]) ? creep
X = [b, c, a] 

如果有人能用自然语言向我解释递归的作用以及如何阅读子句,我将不胜感激。与第二个子句一样,它读作“从列表H|T 中删除重复项并输出Result 如果该列表的头部是尾部的成员并从尾部删除重复项并输出结果是否正确?但是这两个Results怎么会一样呢?而且我也不知道什么时候激活的规则。它什么时候在我的条款列表中前进?它什么时候回去?

抱歉所有问题。我真的很想了解一切。

【问题讨论】:

  • 您的编辑从根本上改变了问题,并使答案无效。我们不应该在 SO 上这样做,所以我要回滚。

标签: recursion prolog


【解决方案1】:

任何递归实现都至少有两个子句 - 基础子句和一个或多个递归子句。

基本子句处理退化情况:空列表、零等。他们给出了简单的答案 - 例如,在您的情况下,基本子句指出空列表的答案是空列表。

递归子句分别处理项目在列表中的情况(第二个子句)和项目不在列表中的情况(第三个子句)。第二个子句说,当在列表的尾部(T 的成员)找到一个项目时,现在不应该将它添加到结果中;稍后会添加。另一方面,如果这是最后一项,则第三个子句将其添加到输出列表中。

但是,最终,成员检查将失败(b 不是[c,a] 的成员),但它是如何工作的呢?

一旦成员检查失败,Prolog 进入第三个子句,将H 添加到带有[H|Result] 的输出列表中,并从列表的尾部T 继续计算Result 的其余部分redu(T, Result)

注意 1: 程序中有一个错误:最后一个子句需要以 not 项位于列表尾部为条件:

redu([H|T], [H|Result]):-
   \+ member(H,T),
   redu(T, Result).

如果第二个子句已经执行,这应该可以防止 Prolog 进入该子句。

注意 2: 另一个选项是在第二个子句中使用 cut,但强烈建议不要使用此选项。

【讨论】:

  • 好的,所以当有重复项时我会得到它:如果成员检查成功,则激活 redu(T, Result) 规则,之后序言重新滚动到开头。新列表(即尾部)首先与第一个子句匹配,但没有匹配,它继续到第二个,匹配并再次激活 membercheck。但是,最终,membercheck 将失败(b 不是 c,a 的成员),但它是如何工作的呢?第二个子句的第三部分是否又被激活了?它会回到最初吗?整个第二个子句突然失效了吗?
  • @das 仍然会包含重复项,但在第一个结果之后的 next 结果中 - 代码中没有削减,co 中也没有相互排斥的目标-匹配子句。
  • @WillNess 你说得对,我假设第二个子句中有感叹号,但它不存在。非常感谢您的评论!
  • \+! 一样不纯。此外,我认为这个问题更像是“帮助我理解”,而不是“帮助我实施”。
【解决方案2】:

所以你有

redu([], []).
redu([H|T],    R ):- member(H, T), redu(T, R).
redu([H|T], [H|R]):-               redu(T, R).
==
redu([], []).
redu([H|T], X ):-   member(H, T), X =    R , redu(T, R).
redu([H|T], X):-                  X = [H|R], redu(T, R).
==
redu([], []).
redu([H|T], X ):- ( member(H, T), X =    R 
                    ;             X = [H|R]), redu(T, R).
==
redu([], []).
redu([H|T], X ):- disj(H, T, X, R), redu(T, R).

disj(H, T,    R,  R):- member(H, T).
disj(H,_T, [H|R], R).

这两个新的redu/2子句是互斥的,所以这种形式的代码可以更容易理解。 disj/4 是否将 H 包含到正在构建的列表 X 中(以自上而下的方式) - 不管它成功了多少次 (*) - 在 disj/4 完成它的事情之后,对 @ 的递归调用987654327@简单制作。

所以我们将redu(L,X) 读作“对于L=[H|T] 中的头部元素H,如果在T 中有更多Hs,要么不要在“输出”列表X中包含H,或者做;对于一个独特的H - 这样不会出现在T中 - 总是将它包含在X中; 然后,处理完L的这个头元素H,继续以相同的方式处理列表中的其余元素我>。”换句话说,对列表L 中的每个 元素执行this

这个递归定义自然地遵循列表的归纳定义为 [H|T][] 结构。

(*)(请注意,A.member 可能会成功多次,而B.disj/4 的两个子句是相互排斥)。

以你的例子,

redu([a,b,b,c,a], X)
==
disj( a, [b,b,c,a], X,R),             %  AND  redu([b,b,c,a], R)   i.e.
    disj( b, [b,c,a], R,R2),            %  AND  redu([b,c,a], R2)  i.e.
        disj( b, [c,a], R2,R3),           %  AND  redu([c,a], R3)  i.e.
            disj( c, [a],  R3,R4),          %  AND  redu([a], R4)  i.e.
                disj( a, [],  R4,R5),         %  AND  the final clause,
                   redu( [],     R5).

现在您可以尝试每个disj/4 调用,看看那里发生了什么,比如

33 ?- disj(a,[b,b,c,a], X,R).
X = R ;
X = [a|R].

34 ?- disj(b,[c,a], R2,R3).
R2 = [b|R3].

这样整个例子就变成了

(X = R ; X = [a|R]),                          % [ a
    (R = R2 ; R = [b|R2]),                    %   b
         R2 = [b|R3],                         %   b
                 R3 = [c|R4],                 %   c
                         R4 = [a|R5],         %   a
                                 R5 = [].     % ]

ex(X):-
 (X = R ; X = [a|R]), 
     (R = R2 ; R = [b|R2]), 
          R2 = [b,c,a].

这是

42 ?- ex(X).
X = [b, c, a] ;
X = [b, b, c, a] ;
X = [a, b, c, a] ;
X = [a, b, b, c, a].

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 2016-10-04
    • 2022-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多