Recursion 请解释这个Prolog递归的例子

Recursion 请解释这个Prolog递归的例子,recursion,prolog,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).

我正在学习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
(如果该列表的头部是尾部的成员),然后从尾部删除重复项并输出结果?”?但是这两个
结果怎么可能相同呢?我也不知道什么时候激活了哪个规则。什么时候在我的条款清单中继续?什么时候回去


很抱歉问了这么多问题。我真的想了解一切。

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

基本子句处理退化情况:空列表、零等。它们给出了简单的答案——例如,在您的例子中,base子句说明空列表的答案是空列表

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

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

一旦成员检查失败,Prolog将转到第三个子句,该子句使用
[H | Result]
H
添加到输出列表中,并继续使用
redu(T,Result)
从列表的尾部
T
计算剩余的
结果

注1:程序中有一个错误:最后一个子句的条件是该项不在列表的尾部:

redu([H|T], [H|Result]):-
   \+ member(H,T),
   redu(T, Result).
如果第二个子句已经执行,这应该可以防止Prolog进入该子句

注2:另一个选项是在第二个子句中使用cut,但是这个选项是非常不推荐的。

所以你有

redu([],[])。
redu([H | T],R):-成员(H,T),redu(T,R)。
redu([H | T],[H | R]):-redu(T,R)。
==
redu([],[])。
redu([H | T],X):-成员(H,T),X=R,redu(T,R)。
redu([H | T],X):-X=[H | R],redu(T,R)。
==
redu([],[])。
redu([H | T],X):-(成员(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):-成员(H,T)。
disj(H,| T,[H | R],R)。
这两个新的
redu/2
子句是互斥的,因此代码在这种形式下更容易理解。无论
disj/4
是否将
H
包括在正在构建的
X
列表中(以自顶向下的方式),无论它成功了多少次(*),在
disj/4
完成它的工作之后,对
redu/2
的递归调用都是简单的

因此我们将
redu(L,X)
as解读为
L=[H | T]中的头元素
H
,如果在
T
中有更多的
H
,要么不将
H
包括在“输出”列表
X
中,要么就这样做;对于唯一的
H
,例如在
T
中不会出现的
H
,总是将其包括在
X
中;然后,处理完
的这个头元素
H
>L
,继续以相同的方式处理列表中的其余元素。”换句话说,对列表中的每个元素执行此操作

这种递归定义自然遵循列表的归纳定义,即
[H | T]
[]
结构

(*)(请注意,A.
成员
可能多次成功,B.disj/4
的两个子句并不相互排斥)

以你为例,

redu([a,b,b,c,a],X)
==
disj(a[b,b,c,a],X,R),%和redu([b,b,c,a],R)即。
disj(b[b,c,a],R,R2),%和redu([b,c,a],R2)即。
disj(b,[c,a],R2,R3),%和redu([c,a],R3)即。
disj(c[a],R3,R4),%和redu([a],R4)即。
disj(a,[],R4,R5),%和最终条款,
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]。

好的,当存在重复项时,我会得到它:如果membercheck成功,则会激活redu(T,Result)规则,然后prolog会重新滚动到开头。新列表(即尾部)首先与第一个子句匹配,但没有匹配,它继续到第二个,有一个匹配,membercheck被激活