【问题标题】:Intersection and union of 2 lists2个列表的交集和并集
【发布时间】:2012-03-25 18:41:58
【问题描述】:

我正在开始学习 prolog(我使用 SWI-prolog),我做了一个简单的练习,其中我有 2 个列表,我想计算它们的交集和并集。 这是我的代码,效果很好,但我问自己是否有更好的方法,因为我不喜欢使用CUT operator

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

请记住,我只想得到 1 个结果,而不是多个结果(即使正确),因此使用以下代码运行代码:

?- intersect([1,3,5,2,4] ,[6,1,2]).

应该退出:

[1,2]
true.

而不是与

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

对于联合谓词必须同样有效。
正如我所说,我的代码运行良好,但请提出更好的方法。
谢谢

【问题讨论】:

    标签: list prolog


    【解决方案1】:

    试试这个,类似于 union/3 here:

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

    【讨论】:

      【解决方案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].
      

      【讨论】:

      • 我注意到我使用的是内置的交集方法。所以这段代码不起作用。
      【解决方案3】:

      以下是根据我的previous answerRemove duplicates in list (Prolog); 其基本思想依次是基于@false's answerProlog union for A U B U C

      我想向你传达什么信息?

      • 您可以用逻辑纯度在 Prolog 中描述您想要的内容。
      • 使用if_/3(=)/3 可以实现逻辑纯
        • 都高效(仅在需要时留下选择点)
        • 和单调(在泛化/专业化方面合乎逻辑)。
      • @false 的谓词 if_/3(=)/3 的实现确实在内部使用元逻辑 Prolog 功能,但(从外部)在逻辑上表现纯粹

      list_list_intersection/3list_list_union/3 的以下实现使用list_item_isMember/3list_item_subtracted/3,在previous answer 中定义:

      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,定义为here

      现在让我们看看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,上述查询不会存在终止。

      他们用新的。 由于解决方案集大小是无限的,因此这些查询都不会普遍终止。

      【讨论】:

      • 这是否可行? list_list_unionSet(A,[],[a,b]).
      • @GuyCoder。谢谢!现在应该(稍微)好一些。仅供参考,引用的“以前的答案”也被编辑了。
      【解决方案4】:

      % 元素 X 在列表中?

      pert(X, [ X | _ ]).

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

      % 两个列表的并集

      联合([ ],L,L)。

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

      联合([_|L1],L2,L3):-联合(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)。

      【讨论】:

        【解决方案5】:

        最后(真的),您可以使用 findall 找到所有解决方案,然后使用 nth0 提取第一个解决方案,这将为您提供您想要的结果而无需削减,并保持谓词干净整洁,没有任何额外的谓词来捕获/停止 prolog 做它最擅长的事情 - 回溯并找到多个答案。

        编辑:有争议的是,在“核心逻辑”中添加额外的谓词以防止生成多个结果,这与使用您试图避免的削减一样丑陋/令人困惑。但也许这是一个学术练习,证明它可以在不使用 findall 等高阶谓词或内置交集/联合的情况下完成。

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

        【讨论】:

          【解决方案6】:

          另外,不知道你为什么反对削减,只要他们的删除不会改变代码的声明性含义,根据你的链接。例如:

          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:不用剪了,看另一个答案!
          【解决方案7】:

          为了比我的第一个答案略少作弊,您可以使用 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].
          

          【讨论】:

            【解决方案8】:

            如果目标只是“完成工作”,那么 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].
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-02-06
              • 2019-07-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多