Prolog 如何将 2 个列表打印为一个列表,而不需要任何附加代码?

发布于 2024-12-19 10:27:19 字数 541 浏览 1 评论 0原文

我有以下代码,这显然是显示两个列表之间并集的标准方法:

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 在递归调用中永远不会改变,但最后,它会打印出在两个原始列表之间形成并集的所有元素。

请帮助我理解这段代码。

谢谢。

I have the following code, which is apparently the standard way to show the union between 2 lists:

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).

and on the following input:

union([a,b,c,d,2,3], [b,c,3,99], Result).

will give me the following output:

Result = [a,d,2,b,c,3,99] ? 

yes

My question is, How does prolog do this? List2 is never changed throught the recursive calls, but at the end, it prints out all elements that make the union between the 2 original lists.

Please help me understand this code.

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

幸福不弃 2024-12-26 10:27:19

假设您询问 union([1,2],[2],R)。

根据第一条规则, union([1|[2]],[2],R) 为 true,如果
成员(1,[2]) -->错误的
然后 prolog 将检查第二条规则 union([1|[2]],[2],[1|R]) 是否为真
+成员(1,[2]) -->真的
和 union([2],[2],R)

现在, union([2|[]],[2],R) 将为 true(第一条规则)如果
成员(2,[2]) --> true
并且 union([],[2],R)

union([],[2],R) 将为 true(第三条规则),如果 R=[2]

那么 R=[2] ,因此对 union 的第一次调用返回[1|[2]] = [1,2]

一个了解“prolog 是如何实现的”的有用工具是trace/0:

    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 的唯一元素

let's assume that you ask union([1,2],[2],R).

according to the first rule, union([1|[2]],[2],R) would be true if
member(1,[2]) --> false
then prolog will check the second rule union([1|[2]],[2],[1|R]) will be true if
+member(1,[2]) --> true
and union([2],[2],R)

now, union([2|[]],[2],R) would be true (1st rule) if
member(2,[2]) -->true
and union([],[2],R)

union([],[2],R) would be true (3rd rule) if R=[2]

so R=[2] and therefore the first call to union returns [1|[2]] = [1,2]

a useful tool to find out "how prolog does it" is trace/0:

    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] .

all in all: List2 doesnt not change but the predicate does not return List2 either; it returns a list created by List2 and the unique elements of List1

梦萦几度 2024-12-26 10:27:19

这里的算法如下:

1) 将结果初始化为List2。这部分的实现归功于:

union([], List2, List2).

2) 遍历 List1 并对每个项目执行以下操作:
2a) 如果该项目不在 List2 中,则将其添加到 Result 中。该部分的实现要归功于以下子句:

union([Head|Tail], List2, [Head|Result]) :- 
    \+ member(Head, List2),
    union(Tail, List2, Result).

2b) 如果该项目位于 List2 中,则不执行任何操作。该部分的实现要归功于以下子句:

union([Head|Tail], List2, Result) :-
    member(Head, List2),
    union(Tail, List2, Result).

要逐步理解序言执行,请参阅@thanosQR答案。

顺便说一句,请注意,此谓词需要设置才能返回良好的并集,否则,List1 中的重复项将在 Result 中保留重复项(List1 中的重复项也会如此) >列表2)。

The algorithm in work here is the following :

1) initialize the result to List2. This part is implemented thanks to :

union([], List2, List2).

2) go through List1 and do the following for each item :
2a) add it to Result if the item is not in List2. That part is implemented thanks to this clause :

union([Head|Tail], List2, [Head|Result]) :- 
    \+ member(Head, List2),
    union(Tail, List2, Result).

2b) don't do anything if the item is in List2. That part is implemented thanks to this clause :

union([Head|Tail], List2, Result) :-
    member(Head, List2),
    union(Tail, List2, Result).

For step by step comprehension of the prolog execution, please refer to @thanosQR answer.

By the way, note that this predicate needs sets to return a good union, else, a duplicate in List1 will stay a duplicate in Result (so will a duplicate in List2).

酒浓于脸红 2024-12-26 10:27:19

该代码实际上效率相当低,因此我们不能将其视为计算并集的“标准方法”。乍一看,有两个简单的优化:避免重复成员资格测试,并使用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),
...

我们从 62,532,499 推论传递到 20,002 推论,并且测试不会强制评估所有替代方案(来自成员的回溯点):添加此我们需要25,015,004更多的推论,尽管没有更多的解决方案可用。这里是来自 SWI-Prolog lists 库的代码:

%%  union(+Set1, +Set2, -Set3) is det.
%
%   True if Set3 unifies with the union of Set1 and Set2.
%   The complexity of this predicate is |Set1|*|Set2|
%
%   @see ord_union/3.

union([], L, L) :- !.
union([H|T], L, R) :-
    memberchk(H, L), !,
    union(T, L, R).
union([H|T], L, [H|R]) :-
    union(T, L, R).

它与您的版本相似,请注意剪切 (!)

That code it's actually rather inefficient, so we can't assume it as the 'standard way' to compute union. At first glance, there are 2 simple optimizations: avoid repeat the membership test, and use memberchk/2 instead of member/2. So we can rewrite it in the following way:

union([Head|Tail], List2, ResultT) :-
    (  memberchk(Head, List2)
    -> ResultT = Result
    ;  ResultT = [Head|Result] ),
    union(Tail, List2, Result).
union([],List2,List2).

The difference in performance is huge. With relatively small lists:

...
numlist(1, 10000, A),
numlist(5000, 10000, B),
union(A, B, C),
...

we pass from 62,532,499 inferences to 20,002 inferences, and the test doesn't force the evaluation of all alternatives (backtrack points from member): adding this we need 25,015,004 more inferences, albeit no more solution is available. Here the code from SWI-Prolog lists library:

%%  union(+Set1, +Set2, -Set3) is det.
%
%   True if Set3 unifies with the union of Set1 and Set2.
%   The complexity of this predicate is |Set1|*|Set2|
%
%   @see ord_union/3.

union([], L, L) :- !.
union([H|T], L, R) :-
    memberchk(H, L), !,
    union(T, L, R).
union([H|T], L, [H|R]) :-
    union(T, L, R).

It's similar to your version, please note the cut (!)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文