两个列表的交集和并集

8

我开始学习Prolog(我使用SWI-Prolog),做了一个简单的练习,其中有2个列表,我想计算它们的交集和并集。这是我的代码,运行得非常好,但我在想是否有更好的方法来实现它,因为我不喜欢使用CUT操作符

intersectionTR(_, [], []).
intersectionTR([], _, []).
intersectionTR([H1|T1], L2, [H1|L]):-
    member(H1, L2),
    intersectionTR(T1, L2, L), !.
intersectionTR([_|T1], L2, L):-
    intersectionTR(T1, L2, L).

intersection(L1, L2):-
    intersectionTR(L1, L2, L),
    write(L).


unionTR([], [], []).
unionTR([], [H2|T2], [H2|L]):-
    intersectionTR(T2, L, Res),
    Res = [],
    unionTR([], T2, L),
    !.
unionTR([], [_|T2], L):-
    unionTR([], T2, L),
    !.

unionTR([H1|T1], L2, L):-
    intersectionTR([H1], L, Res),
    Res \= [],
    unionTR(T1, L2, L).
unionTR([H1|T1], L2, [H1|L]):-
    unionTR(T1, L2, L).

union(L1, L2):-
    unionTR(L1, L2, L),
    write(L).

记住,我希望只得到一个结果,即使有多个正确的结果,所以在运行以下代码时,请注意:
?- intersect([1,3,5,2,4] ,[6,1,2]).

应该退出并显示:
[1,2]
true.

而不是与之

[1,2]
true ;
[1,2]
true ;
etc...

同样的要求也适用于联合谓词。
就像我说的,我的代码运行得很好,但请建议更好的方法来完成它。
谢谢。
8个回答

9
此外,不确定您为什么坚决反对删减,只要删除不会改变代码的声明性含义,就像您提供的链接一样。例如:
inter([], _, []).

inter([H1|T1], L2, [H1|Res]) :-
    member(H1, L2),
    inter(T1, L2, Res).

inter([_|T1], L2, Res) :-
    inter(T1, L2, Res).

test(X):-
        inter([1,3,5,2,4], [6,1,2], X), !.

test(X).
X = [1, 2].

在我调用代码的测试比特中,我只想找到第一个交集。谓词定义本身没有任何削减。


我会接受这个答案,因为它是最正确的。经过深入调查,我明白了,由于非确定性,我所问的问题根本不可能在没有CUT的情况下实现。是的,以这种方式使用CUT并不会修改程序的声明性质。非常感谢! - andreapier
1
andreapier: 不需要剪切,可以看另一个答案! - false

6
以下内容基于我对“列表中去除重复项(Prolog)”问题的先前回答;基本思想又来源于A U B U C 的 Prolog 并集问题的@false的回答
我想要传达什么信息给你?
  • 您可以使用逻辑纯度来描述Prolog中所需的内容。
  • 使用if_/3(=)/3,逻辑纯实现可以同时具有以下特点:
    • 高效(仅在需要时留下选择点)
    • 单调(在一般化/特化方面逻辑上正确)。
  • @false谓词if_/3(=)/3的实现确实在内部使用元逻辑Prolog功能,但(从外部)表现为逻辑纯

下面的list_list_intersection/3list_list_union/3实现使用list_item_isMember/3list_item_subtracted/3,这些函数在之前的答案中定义:

list_list_union([],Bs,Bs).
list_list_union([A|As],Bs1,[A|Cs]) :-
    list_item_subtracted(Bs1,A,Bs),
    list_list_union(As,Bs,Cs).

list_list_intersection([],_,[]).
list_list_intersection([A|As],Bs,Cs1) :-
    if_(list_item_isMember(Bs,A), Cs1 = [A|Cs], Cs1 = Cs),
    list_list_intersection(As,Bs,Cs).

以下是您在问题中发布的查询:

?- list_list_intersection([1,3,5,2,4],[6,1,2],Intersection).
Intersection = [1, 2].                    % succeeds deterministically

让我们尝试其他的东西...以下两个查询应该在逻辑上等价:

?- A=1,B=3, list_list_intersection([1,3,5,2,4],[A,B],Intersection).
A = 1,
B = 3,
Intersection = [1, 3].
?- list_list_intersection([1,3,5,2,4],[A,B],Intersection),A=1,B=3.
A = 1,
B = 3,
Intersection = [1, 3] ;
false.

那么...最终结论是什么?

  • 使用纯代码很容易保持逻辑的正确性。
  • 而不纯的代码则往往表现得像“它应该做的那样”,但在像上面展示的查询中显示出各种非逻辑行为。

编辑2015-04-23

list_list_union(As,Bs,Cs)list_list_intersection(As,Bs,Cs)都不能保证Cs不包含重复元素。如果这让您感到困扰,需要适当修改代码。

以下是一些更多的查询(及其答案),当中As和/或Bs包含重复元素:

?- list_list_intersection([1,3,5,7,1,3,5,7],[1,2,3,1,2,3],Cs).
Cs = [1, 3, 1, 3].
?- list_list_intersection([1,2,3],[1,1,1,1],Cs).
Cs = [1].

?- list_list_union([1,3,5,1,3,5],[1,2,3,1,2,3],Cs).
Cs = [1, 3, 5, 1, 3, 5, 2, 2]. 
?- list_list_union([1,2,3],[1,1,1,1],Cs).
Cs = [1, 2, 3].
?- list_list_union([1,1,1,1],[1,2,3],Cs).
Cs = [1, 1, 1, 1, 2, 3].

编辑2015-04-24

为了完整起见,以下是如何强制要求交集和并集为集合---即不包含任何重复元素的列表。

以下代码非常直观:

list_list_intersectionSet([],_,[]).
list_list_intersectionSet([A|As1],Bs,Cs1) :-
    if_(list_item_isMember(Bs,A), Cs1 = [A|Cs], Cs1 = Cs),
    list_item_subtracted(As1,A,As),
    list_list_intersectionSet(As,Bs,Cs).

list_list_unionSet([],Bs1,Bs) :-
    list_setB(Bs1,Bs).
list_list_unionSet([A|As1],Bs1,[A|Cs]) :-
    list_item_subtracted(As1,A,As),
    list_item_subtracted(Bs1,A,Bs),
    list_list_unionSet(As,Bs,Cs).

请注意,list_list_unionSet/3 基于 list_setB/2,在 这里 定义。
现在让我们看看 list_list_intersectionSet/3list_list_unionSet/3 的实际应用:
?- list_list_unionSet([1,2,3,1,2,3,3,2,1],[4,5,6,2,7,7,7],Xs).
Xs = [1, 2, 3, 4, 5, 6, 7].

?- list_list_intersectionSet([1,2,3,1,2,3,3,2,1],[4,5,6,2,7,7,7],Xs).
Xs = [2].

编辑于2019-01-30

以下是从@GuyCoder的评论中提取的一个额外的查询(以及两个变体):

?- list_list_unionSet(Xs,[],[a,b])。
   Xs = [a,b]
;  Xs = [a,b,b]
;  Xs = [a,b,b,b]
...
?- list_list_unionSet([],Xs,[a,b])。 Xs = [a,b] ; Xs = [a,b,b] ; Xs = [a,b,b,b] ...
?- list_list_unionSet(Xs,Ys,[a,b])。 Xs = [],Ys = [a,b] ; Xs = [],Ys = [a,b,b] ; Xs = [],Ys = [a,b,b,b] ...

使用旧版本的list_item_subtracted/3,上述查询不存在终止。

使用新版本后它们会终止。由于解集大小是无限的,因此这些查询都不会普遍终止。


1
这个可以工作吗?list_list_unionSet(A,[],[a,b]). - Guy Coder
@GuyCoder。谢谢!现在应该(稍微)好一些了。FYI,之前提到的“先前的答案”也被编辑过了。 - repeat

3
为了比我第一个答案稍微少作弊一点,你可以使用findall高阶谓词,这个谓词让Prolog为你完成递归:
4 ?- L1=[1,3,5,2,4], L2=[6,1,2], findall(X, (nth0(N, L1, X), member(X, L2)), Res).
L1 = [1, 3, 5, 2, 4],
L2 = [6, 1, 2],
Res = [1, 2].

1
如果目标只是“完成工作”,那么SWI Prolog具有专门用于此目的的内置原语:
[trace] 3 ?- intersection([1,3,5,2,4] ,[6,1,2], X).
intersection([1,3,5,2,4] ,[6,1,2], X).
X = [1, 2].

[trace] 4 ?- union([1,3,5,2,4] ,[6,1,2], X).
X = [3, 5, 4, 6, 1, 2].

0
尝试这个,类似于 union/3 这里
:- use_module(library(clpfd)).

member(_, [], 0).
member(X, [Y|Z], B) :-
   (X #= Y) #\/ C #<==> B,
   member(X, Z, C).

intersect([], _, []).
intersect([X|Y], Z, T) :-
   freeze(B, (B==1 -> T=[X|R]; T=R)),
   member(X, Z, B),
   intersect(Y, Z, R).

如果元素是整数,它就能工作,并且不会留下任何选择点:

?- intersect([X,Y],[Y,Z],L).
freeze(_15070,  (_15070==1->L=[X, Y];L=[Y])),
_15070 in 0..1,
_15166#\/_15168#<==>_15070,
_15166 in 0..1,
X#=Y#<==>_15166,
X#=Z#<==>_15168,
Y#=Z#<==>_15258,
_15168 in 0..1,
_15258 in 0..1.

?- intersect([X,Y],[Y,Z],L), X=1, Y=2, Z=3.
X = 1,
Y = 2,
Z = 3,
L = [2].

?- intersect([X,Y],[Y,Z],L), X=3, Y=2, Z=3.
X = Z, Z = 3,
Y = 2,
L = [3, 2].

-1

最后(真的),您可以使用findall查找所有解决方案,然后使用nth0提取第一个,这将为您提供所需的结果,而无需切割,并使谓词保持干净,而不需要任何附加的谓词来截住/停止Prolog做它最擅长的事情-回溯和查找多个答案。

编辑:可以说,在“核心逻辑”中放置额外的谓词以防止生成多个结果与您试图避免的割裂一样丑陋/令人困惑。但也许这是一个学术练习,证明可以在不使用高阶谓词(如findall或内置的intersection/union)的情况下完成它。

inter([], _, []).

inter([H1|T1], L2, [H1|Res]) :-
    member(H1, L2),
    inter(T1, L2, Res).

inter([_|T1], L2, Res) :-
    inter(T1, L2, Res).

test(First):-
        findall(Ans, inter([1,3,5,2,4], [6,1,2], Ans), Ansl), 
        nth0(0, Ansl, First).

-2

% 元素 X 是否在列表中?

pert(X, [ X | _ ]).

pert(X, [ _ | L ]):- pert(X, L).

% 两个列表的并集

union([ ], L, L).

union([ X | L1 ], L2, [ X | L3 ]):- \+pert(X, L2), union(L1, L2, L3).

union([ _ | L1 ], L2, L3):- union(L1, L2, L3).

% 两个列表的交集

inter([ ], _, [ ]).

inter([ X | L1 ], L2, [ X | L3 ]):- pert(X, L2), inter(L1, L2, L3).

inter([ _ | L1 ], L2, L3):- inter(L1, L2, L3).


-2

我知道这篇文章很老,但我找到了一种最小化编码的解决方案。

% intersection
intersection([],L1,L2,L3).
intersection([H|T],L2,L3,[H|L4]):-member(H,L2),intersection(T,L3,L3,L4).
% member
member(H,[H|T]).
member(X,[H|T]):-member(X,T).

为了测试上述代码,您不应该输入 L3。以下是一个示例。
?- intersection([w,4,g,0,v,45,6],[x,45,d,w,30,0],L).
L = [w, 0, 45].

1
我注意到我正在使用内置的交集方法,因此这段代码不起作用。 - Saeid Zarrinmehr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接