List Prolog如何将两个列表打印为一个列表,而不使用任何附加代码?
我有以下代码,这显然是显示两个列表之间的并集的标准方式:List Prolog如何将两个列表打印为一个列表,而不使用任何附加代码?,list,prolog,union,member,List,Prolog,Union,Member,我有以下代码,这显然是显示两个列表之间的并集的标准方式: union([Head|Tail],List2,Result) :- member(Head,List2), union(Tail,List2,Result). union([Head|Tail],List2,[Head|Result]) :- \+ member(Head,List2), union(Tail,List2,Result). union([],List2,List2). 并根据以下输入: union
union([Head|Tail],List2,Result) :-
member(Head,List2), union(Tail,List2,Result).
union([Head|Tail],List2,[Head|Result]) :-
\+ member(Head,List2), union(Tail,List2,Result).
union([],List2,List2).
并根据以下输入:
union([a,b,c,d,2,3], [b,c,3,99], Result).
将给我以下输出:
Result = [a,d,2,b,c,3,99] ?
yes
我的问题是,prolog是如何做到这一点的?List2从来不会通过递归调用进行更改,但在最后,它会打印出构成两个原始列表之间的并集的所有元素
请帮我理解这个代码
谢谢。假设您询问union([1,2],[2],R) 根据第一条规则,如果 成员(1,[2])-->false 然后prolog将检查第二个规则联合([1 |[2]],[2],[1 | R])是否为真,如果 +成员(1,[2])-->true 和并集([2],[2],R) 现在,如果 成员(2,[2])-->true 和联合([],[2],R) 如果R=[2],则联合([],[2],R)将为真(第三条规则) 因此R=[2],因此对union的第一次调用返回[1 |[2]]=[1,2] 跟踪/0是了解“prolog是如何工作的”的一个有用工具:
2 ?- trace.
true.
[trace] 2 ?- union([1,2],[2],R).
Call: (6) union([1, 2], [2], _G543) ? creep
Call: (7) lists:member(1, [2]) ? creep
Fail: (7) lists:member(1, [2]) ? creep
Redo: (6) union([1, 2], [2], _G543) ? creep
Call: (7) lists:member(1, [2]) ? creep
Fail: (7) lists:member(1, [2]) ? creep
Call: (7) union([2], [2], _G619) ? creep
Call: (8) lists:member(2, [2]) ? creep
Exit: (8) lists:member(2, [2]) ? creep
Call: (8) union([], [2], _G619) ? creep
Exit: (8) union([], [2], [2]) ? creep
Exit: (7) union([2], [2], [2]) ? creep
Exit: (6) union([1, 2], [2], [1, 2]) ? creep
R = [1, 2] .
总而言之:List2没有改变,但是谓词也没有返回List2;它返回由List2创建的列表和List1的唯一元素。这里使用的算法如下: 1) 将结果初始化为
List2
。本部分的实施得益于:
union([], List2, List2).
2) 浏览列表1
并对每个项目执行以下操作:2a)如果项目不在列表2中,则将其添加到结果中。该部分的实施得益于本条款:
union([Head|Tail], List2, [Head|Result]) :-
\+ member(Head, List2),
union(Tail, List2, Result).
union([Head|Tail], List2, Result) :-
member(Head, List2),
union(Tail, List2, Result).
2b)如果项目在清单2中,则不要做任何事情。该部分的实施得益于本条款:
union([Head|Tail], List2, [Head|Result]) :-
\+ member(Head, List2),
union(Tail, List2, Result).
union([Head|Tail], List2, Result) :-
member(Head, List2),
union(Tail, List2, Result).
对于序言执行的逐步理解,请参考@thanosQR答案
顺便说一句,注意这个谓词需要集合来返回一个好的并集,否则,
List1
中的一个副本将在Result
中保持一个副本(在List2
中的一个副本也是如此)。这段代码实际上效率很低,所以我们不能假设它是计算并集的“标准方法”。乍一看,有两个简单的优化:避免重复成员资格测试,使用memberchk/2而不是member/2。因此,我们可以用以下方式重写它:
union([Head|Tail], List2, ResultT) :-
( memberchk(Head, List2)
-> ResultT = Result
; ResultT = [Head|Result] ),
union(Tail, List2, Result).
union([],List2,List2).
性能上的差异是巨大的。相对较小的列表:
...
numlist(1, 10000, A),
numlist(5000, 10000, B),
union(A, B, C),
...
我们将62532499个推论传递到20002个推论,并且测试不会强制评估所有备选方案(来自成员的回溯点):加上这一点,我们还需要25015004个推论,尽管没有更多的解决方案可用。这里,SWI Prolog中的代码列出了库:
它与您的版本类似,请注意,当程序到达最基本的递归调用时,cut(!)so:union([],List2,List2)。它将开始向上跳转,将列表2向上发送到:union([Head | Tail],List2,[Head | Result]),第三个参数中的[Head | Result]将使用列表2(Result)构造Head?然后继续构建结果,在每个递归级别的列表向上移动时,取列表的开头?是的,这就是它的工作方式,它从结尾构建结果(正如你所期望的那样,使用Head | Tail范例)。因此,为了实现这一点,“函数调用”联合中的“变量”([Head | Tail],List2,[Head | result]),必须从左到右初始化。首先,我们有一个列表,它被分解成两个varaibels头和尾,然后我们有一个名为list 2的变量,然后我们有第三个变量,它是由于Head已经存在而构造的。如果最后一个变量中不存在Head,会发生什么?progol如何区分第一个变量是列表分解变量和第三个变量是构造列表变量?prolog只是试图使谓词为真。这意味着,如果一个列表是一个自由变量(比如你的结果列表),prolog将尝试使它适合一种允许谓词为true的格式。在本例中,通过将其初始化为List2并按照前面的详细说明构造它……这是一种非常奇怪和混乱的语言。哇,我甚至不知道在prolog中可以有变量和赋值。我得研究一下你找到工会的新方法。它看起来比我最初做的方式简单。在序言中,“赋值”被称为统一,这是一个与“逻辑”变量相关的概念。它不是赋值,而是,把它看作模式匹配:)