请解释这个Prolog递归的例子

时间:2017-09-17 17:07:31

标签: recursion prolog

我正在学习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怎么可能是相同的呢?而且我也没有得到哪个规则被激活。它什么时候在我的条款列表中继续?它什么时候回来?

对不起所有问题。我真的很想了解一切。

2 个答案:

答案 0 :(得分:0)

任何递归实现都至少有两个子句 - 基本子句,以及一个或多个递归子句。

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

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

  

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

一旦成员检查失败,Prolog将转到第三个子句,它将H添加到带有[H|Result]的输出列表,然后从尾部开始计算Result的其余部分T列表的redu(T, Result)

注1:但是程序中有一个错误:最后一个句子需要以列表尾部的项目而不是为条件:

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

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

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

答案 1 :(得分:0)

所以你有

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(以自上而下的方式) - 无论多少次成功(*) - redu/2之后它的问题,就是对redu(L,X)的递归调用。

因此,如果 还有一些H s,我们会在L=[H|T]中将H读为“”作为头元素TH中, 在“输出”列表X中不包含H,或者包含T,并且对于唯一的X - 这样在H中不会出现 - 在L总是; 然后中包含它,处理{{1}的头元素L继续以相同的方式处理列表中的其余元素。“换句话说,对列表[H|T]中的每个元素执行 this

这种递归定义自然遵循列表的归纳定义为 [] member 结构。

(*)(请注意 A。 disj/4可能会多次成功, B。 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].

/etc/nginx/sites-available