【问题标题】:Erlang: whether two lists have a common elementErlang:两个列表是否有共同的元素
【发布时间】:2019-05-30 08:49:17
【问题描述】:
a = [1, 2, 3],
b = [4, 5, 6],
c = [1, 7, 8],

has_common_element(a, b)false,因为两个数组之间没有共同的元素。

has_common_element(a, c)true,因为两个数组之间至少有一个公共元素 (1)。

我可以使用lists:member 来检查单个元素是否在列表中。如何在 Erlang 中实现has_common_element 来检查两个列表是否有共同的元素?

【问题讨论】:

    标签: erlang


    【解决方案1】:

    从列表中创建集合并使用sets:is_disjoint

    has_common_member(L1, L2) ->
        not(sets:is_disjoint(sets:from_list(L1), sets:from_list(L2))).
    

    【讨论】:

      【解决方案2】:

      你可以像下面这样使用列表推导:

      test(A, B)->
          [] /= [X || X <- A, Y <- B, X == Y].
      

      shell 中的结果:

      1> A = [1, 2, 3].
      [1,2,3]
      2> B = [4, 5, 6].
      [4,5,6]
      3> C = [1, 7, 8].
      [1,7,8]
      4> foo:test(A, B).
      false
      5> foo:test(A, C).
      true
      

      【讨论】:

        【解决方案3】:

        您可以使用erlang:--/2 运算符快速解决:

        has_common_element(A, B) when length(B) > length(A) ->
            has_common_element(B, A); % not necessary optimization but helps for very uneven sets
        has_common_element(A, B) ->
            A =/= A -- B.
        

        在旧版本中它将是 O(N*M),但由于 OTP21,我认为它变成了 O((N+M)*logM)(我现在找不到相关的发行说明)。在您使用的版本中为自己进行基准测试并正确选择。

        无论如何,您仍然可以使用地图制作非常快的 O((N+M)*logM) 解决方案:

        has_common_element(A, B) when length(B) > length(A) ->
            has_common_element(B, A); % not necessary optimization but helps for very uneven sets
        has_common_element(A, B) ->
            M = maps:from_list([{X,[]} || X <- B]),
            any_in_map(M, A).
        
        any_in_map(_, []) -> false;
        any_in_map(M, [H|T]) ->
            maps:is_key(H, M) orelse any_in_map(M, T).            
        

        还有一个使用 process_dictionary 的旧版本稍快一些。当您对列表元素有很好的控制并且 process_dictionary 不会打扰您时,您可以直接使用它(通常在 OTP 过程中不是一个好主意)。

        has_common_element_dirty(A, B) when length(B) > length(A) ->
            has_common_element_dirty(B, A); % not necessary optimization but helps for very uneven sets
        has_common_element_dirty(A, B) ->
            [put(X, []) || X <- B],
            any_in_dict(A).
        
        any_in_dict([]) -> false;
        any_in_dict([H|T]) ->
            [] =:= get(H) orelse any_in_dict(T).
        

        您可以将其包装在链接的进程中并使其安全:

        has_common_element(A, B) ->
            Self = self(),
            Ref = make_ref(),
            Pid = spawn_link(fun() -> Self ! {Ref, has_common_element_dirty(A, B)} end),
            receive {Ref, Result} -> Result end.
        

        针对您的应用程序和列表大小进行基准测试并进行相应选择,因为所有这些解决方案都具有不同的 GC 和原始性能特征。通常,第一个就足够了。

        Erlang/OTP 22 [erts-10.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
        
        Eshell V10.4  (abort with ^G)
        1> Size=100000, A = [rand:uniform(Size*10000) || _ <- lists:duplicate(Size, 0)], B = [rand:uniform(Size*10000) || _ <- lists:duplicate(Size, 0)], C = [rand:uniform(Size*10000)+Size*10000 || _ <- lists:duplicate(Size, 0)], ok.
        ok
        2> timer:tc(fun() -> A =/= A -- B end).
        {77943,true}
        3> timer:tc(fun() -> A =/= A -- C end).
        {44325,false}
        

        比较两个 100k 列表的时间不到 100 毫秒。它与Pascal 的解决方案中的最佳(s2)相当或更好。注意 OTP22 版本。

        【讨论】:

        • 有趣的是 --/2 的性能得到了改进。
        【解决方案4】:

        @bxdoan 和@choroba 解决方案都有效并且非常简洁。您没有提供有关列表大小及其内容的信息,这可能很重要。

        具有列表理解的解决方案测试一个列表,该列表的大小是 2 个输入列表的叉积,因此对于大输入列表会变得非常慢。具有集合的解决方案增长速度较慢。我向您推荐另一个,不太简洁,但对于较长的列表表现更好。我还添加了一个使用 lists:member 的解决方案(效率不高)和另一个使用 maps 的解决方案(类似于排序)。

        -module (inter).
        
        -export ([test/2,s1/2,s2/2,s3/2,s4/2,s5/2]).
        
        
        
        test(Method,Size) ->
            A = [rand:uniform(Size*10000) || _ <- lists:duplicate(Size, 0)],
            % B may contain common element with A
            B = [rand:uniform(Size*10000) || _ <- lists:duplicate(Size, 0)],
            % C is by construction disjoint with A
            C = [rand:uniform(Size*10000)+Size*10000 || _ <- lists:duplicate(Size, 0)],
            io:format("test algorithm ~s~n",[method(Method)]),
            {test(Method,A,B),test(Method,A,C)}.
        
        
        test(M,L1,L2) ->
            timer:tc(?MODULE, M, [L1,L2]).
        
        method(s1) ->
            "List comprehension";
        method(s2) ->
            "List sorted";
        method(s3) ->
            "List to set";
        method(s4) ->
            "List to map";
        method(s5) ->
            "lists:member".
        
        s1(L1,L2) ->
            [] =/= [X || X <- L1, Y <- L2, X =:= Y].
        
        
        s2(L1,L2) ->
            s2_help(lists:sort(L1),lists:sort(L2)).
        
        s2_help([],_) -> false;
        s2_help(_,[]) -> false;
        s2_help([_A|_],[_A|_]) -> true;
        s2_help([H1|T1],[H2|T2]) when H1 > H2 -> s2_help([H1|T1],T2);
        s2_help(L1,L2) -> s2_help(tl(L1),L2).
        
        s3(L1,L2) -> 
            not(sets:is_disjoint(sets:from_list(L1), sets:from_list(L2))).
        
        s4(L1,L2) ->
            s4_help(lists:foldl(fun(X,Acc) -> maps:put(X, 0, Acc) end,#{},L1),L2).
        
        s4_help(_,[]) -> false;
        s4_help(Map,[H|T]) ->
            case maps:is_key(H, Map) of
                true -> true;
                false -> s4_help(Map,T)
            end.
        
        s5([],_) -> false;
        s5([H|T],L2) ->
            case lists:member(H, L2) of
                true -> true;
                false -> s5(T,L2)
            end.
        

        然后是结果:

        55> c(inter).             
        {ok,inter}
        56> inter:test(s1,100).   
        test algorithm List comprehension
        {{0,false},{0,false}}
        57> inter:test(s2,100).   
        test algorithm List sorted
        {{0,false},{0,false}}
        58> inter:test(s3,100).   
        test algorithm List to set
        {{0,false},{0,false}}
        59> inter:test(s1,1000).
        test algorithm List comprehension
        {{16000,true},{0,false}}
        60> inter:test(s2,1000).
        test algorithm List sorted
        {{0,false},{0,false}}
        61> inter:test(s3,1000).
        test algorithm List to set
        {{0,false},{0,false}}
        62> inter:test(s1,10000).
        test algorithm List comprehension
        {{468999,false},{484000,false}}
        63> inter:test(s2,10000).
        test algorithm List sorted
        {{15000,false},{0,false}}
        64> inter:test(s3,10000).
        test algorithm List to set
        {{31000,false},{32000,false}}
        65> inter:test(s1,100000).
        test algorithm List comprehension
        {{48515953,true},{48030953,false}}
        66> inter:test(s2,100000).
        test algorithm List sorted
        {{62000,true},{78000,false}}
        67> inter:test(s3,100000).
        test algorithm List to set
        {{1233999,true},{1296999,false}}
        ...
        69> inter:test(s4,100).   
        test algorithm List to map
        {{0,false},{0,false}}
        70> inter:test(s4,1000).
        test algorithm List to map
        {{0,false},{0,false}}
        71> inter:test(s4,10000).
        test algorithm List to map
        {{0,false},{16000,false}}
        72> inter:test(s4,100000).
        test algorithm List to map
        {{62000,true},{78000,false}}
        73> inter:test(s5,100).   
        test algorithm lists:member
        {{0,false},{0,false}}
        74> inter:test(s5,1000).
        test algorithm lists:member
        {{0,false},{0,false}}
        75> inter:test(s5,10000).
        test algorithm lists:member
        {{31000,true},{171999,false}}
        76> inter:test(s5,100000).
        test algorithm lists:member
        {{921000,true},{19031980,false}}
        77>
        

        【讨论】:

        • 你可以用lists:usort/1代替lists:sort/1
        • 试试A =/= A -- B ;-)
        猜你喜欢
        • 1970-01-01
        • 2011-08-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-02
        • 1970-01-01
        • 2019-05-16
        • 1970-01-01
        相关资源
        最近更新 更多