ar([],[]).
ar([p(_,_)|L],L1):-ar(L,L2),L1=L2.
ar([p(X,Y)|L],L1):-ar(L,L2),L1=[p(X,Y)|L2].
(p代表点,具有坐标X和Y)
请帮助我理解结果是如何构建的,尤其是L1获得新值的部分,谢谢!
答案 0 :(得分:2)
谓词ar/2
的定义与powerset函数的行为类似,是以下语法的变体(其中X
仅限于p/2
):< / p>
% clause 1: base case
ps([], []).
% clause 2: omit the element X
ps([_X|Y], Z) :-
ps(Y, Z).
% clause 3: keep the element X
ps([X|Y], [X|Z]) :-
ps(Y, Z).
谓词ps/2
(和你的ar/2
)基本上是回溯,将第一个参数中列表的所有子列表绑定到第二个参数的列表。它通过第二个和第三个子句表示的选择来实现这一点:在构造新列表时省略或保留列表元素。
考虑Prolog在执行目标ps([a,b],L)
时所做的事情:
ps([_|[b]], Z) :- ps([b], Z).
(通过第2条,删除a
)。
ps([b|[]], Z) :- ps([], Z).
(通过第2条,删除b
;请注意[b]
= [b|[]]
)。
ps([], Z)
绑定Z = []
(通过第1条,提供解决方案1)。ps([b|[]], [b|Z]) :- ps([], Z).
(通过第3条,保留b
)。
ps([], Z)
绑定Z = []
(通过第1条,提供解决方案2)。ps([_|[b]], [a|Z]) :- ps([b], Z).
(通过第3条,保留a
)。
ps([b|[]], Z) :- ps([], Z).
(通过第2条,删除b
)。
ps([], Z)
绑定Z = []
(通过第1条,提供解决方案3)。ps([b|[]], [b|Z]) :- ps([], Z).
(通过第3条,保留b
)。
ps([], Z)
绑定Z = []
(通过第1条,提供解决方案4)。每个最深层次触及第1条的“基础案例”都会返回调用堆栈。每种情况都会产生以下结果:
a
和b
:[]
a
,保留b
:[b]
a
,放弃b
:[a]
a
和b
:[a,b]
因此,我们可以回溯生成[]
,[b]
,[a]
和[a,b]
,即[a,b]
的四个子列表。
答案 1 :(得分:1)
首先,请注意,此过程不计算排列,而是计算一种子列表:删除“某些”点的列表,其中“some”以一般形式表示(其中一个解是空的假设输入列表格式正确,列表和其他解决方案是原始列表。
如果输入列表格式不正确(它有一个不是“点”的项),则该过程将失败。
现在让我们解释ar/2
的三个子句,这是一个递归过程:
第一句,
ar([], []).
声明如果第一个参数是空列表,那么第二个参数也是输入列表;即,对于空列表,符合过程规则的唯一“子列表”也是空列表。 这也是递归过程的基本情况。
第二个条款,
ar([p(_,_)|L], L1):-ar(L, L2), L1=L2.
可以在不使用L2
变量的情况下重写,因为它最终会与L1
统一:
ar([p(_,_)|L], L1):-ar(L, L1).
此子句正在跳过输入列表的头部并继续递归。返回递归后,它会将结果列表(ar/2
调用的第二个参数)与子句头部的第二个参数统一起来。
第三个条款,
ar([p(X,Y)|L], L1):-ar(L, L2), L1=[p(X,Y)|L2].
可以在不使用L2
变量的情况下通过在子句的开头构建结果列表来重写:
ar([p(X,Y)|L], [p(X,Y)|L1]):-ar(L,L1).
此子句将取输入列表的头部,继续使用尾部递归,然后将子句的头部的第二个参数与所采用的项目以及结果的递归列表统一起来。也就是说,它将保持输入列表的项目(头部)以及递归结果。
另请注意,此过程不可逆,如果使用第一个参数未实例化调用并且第二个参数实例化,它将永远循环。