二郎神;列表理解没有重复

时间:2016-02-29 22:27:05

标签: erlang

我做的事情太糟糕了,但我不知道如何做得更好。

我正在形成名为SomeList的List元素的所有成对总和,但我不想看到重复(我想我想要"所有可能的成对总和"):

sets:to_list(sets:from_list([A+B || A <- SomeList, B <- SomeList]))

SomeList不包含重复项。

这样可行,但效率非常低,因为设置转换之前的原始列表是GIGANTIC。

有更好的方法吗?

4 个答案:

答案 0 :(得分:3)

您只需使用lists:usort/1

即可

lists:usort([X+Y || X <- L, Y <- L]).

如果有重复的机会非常高,那么您可以使用2个循环生成总和并将总和存储在 ets 集中(或使用 map ,I没有检查两者的表现。

7> Inloop = fun Inloop(_,[],_) -> ok; Inloop(Store,[H|T],X) -> ets:insert(Store,{X+H}), Inloop(Store,T,X) end.
#Fun<erl_eval.42.54118792>
8> Outloop = fun Outloop(Store,[],_) -> ok; Outloop(Store,[H|T],List) -> Inloop(Store,List,H), Outloop(Store,T,List) end.
#Fun<erl_eval.42.54118792>
9> Makesum = fun(L) -> S = ets:new(temp,[set]), Outloop(S,L,L), R =ets:foldl(fun({X},Acc) -> [X|Acc] end,[],S), ets:delete(S), R end.
#Fun<erl_eval.6.54118792>
10> Makesum(lists:seq(1,10)).
[15,13,8,11,20,14,16,12,7,3,10,9,19,18,4,17,6,2,5]
11> lists:sort(Makesum(lists:seq(1,10))).
[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
12> 

答案 1 :(得分:2)

此模块允许您比较使用列表理解,设置 ets 时的执行时间。您当然可以为此比较​​添加其他功能:

-module(pairwise).

-export([start/2]).

start(Type, X) ->
    L = lists:seq(1, X),
    timer:tc(fun do/2, [Type, L]).

do(compr, L) ->
    sets:to_list(sets:from_list([A+B || A <- L, B <- L]));

do(set, L) ->
    F = fun(Sum, Set) -> sets:add_element(Sum, Set) end,
    R = fun(Set) -> sets:to_list(Set) end,
    do(L, L, sets:new(), {F, R});

do(ets, L) ->
    F = fun(Sum, Tab) -> ets:insert(Tab, {Sum}), Tab end,
    R = fun(Tab) ->
                Fun = fun({X}, Acc) -> [X|Acc] end,
                Res = ets:foldl(Fun, [], Tab),
                ets:delete(Tab),
                Res
        end,
    do(L, L, ets:new(?MODULE, []), {F, R}).

do([A|AT], [B|BT], S, {F, _} = Funs) -> do([A|AT], BT, F(A+B, S), Funs);
do([_AT], [], S, {_, R}) -> R(S);
do([_A|AT], [], S, Funs) -> do(AT, AT, S, Funs).

结果:

36> {_, Res1} = pairwise:start(compr, 20).
{282,
 [16,32,3,19,35,6,22,38,9,25,12,28,15,31,2,18,34,5,21,37,8,
  24,40,11,27,14,30|...]}
37> {_, Res2} = pairwise:start(set, 20).  
{155,
 [16,32,3,19,35,6,22,38,9,25,12,28,15,31,2,18,34,5,21,37,8,
  24,40,11,27,14,30|...]}
38> {_, Res3} = pairwise:start(ets, 20).  
{96,
 [15,25,13,8,21,24,40,11,26,20,14,28,23,16,12,39,34,36,7,32,
  35,3,33,10,9,19,18|...]}
39> R1=lists:usort(Res1), R2=lists:usort(Res2), R3=lists:usort(Res3).
[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,
 24,25,26,27,28,29,30|...]
40> R1 = R2 = R3.
[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,
 24,25,26,27,28,29,30|...]

最后一行是比较所有函数返回相同的结果,但排序方式不同。

每个结果元组中的第一个数字是从timer:tc(fun do/2, [Type, L]).返回的执行时间。在这个例子中,列表理解为282,为155, ets 为96。

答案 2 :(得分:1)

一种有效的方法是使用foldl而不是列表理解,因为在这种情况下你在每一步上都有一个状态

sets:to_list(
    lists:foldl(fun(A, S1) -> 
        lists:foldl(fun(B, S2) -> 
            sets:add_element(A+B, S2) 
        end, S1, SomeListA) 
    end, sets:new(), SomeListB)).

答案 3 :(得分:0)

此解决方案使其保持相对较快,并使用尽可能多的预编写库代码。

请注意,我使用list:zip / 2而不是numeric +,仅用于说明此方法适用于任何类型的唯一列表的非重复排列。你可能只关心算术,但如果你想要更多,这可以做到。

-export([permute_unique/1]).
permute_unique([]) ->
    [];
permute_unique([A|Ab]) ->
    lists:zip(lists:duplicate(length(Ab)+1, A), [A|Ab])
    ++
    permute_unique(Ab).

%to sum integers, replace the lists:zip... line with
%   [B+C || {B,C} <- lists:zip(lists:duplicate(length(Ab)+1, A), [A|Ab])]
%to perform normal arithmetic and yield a numeric value for each element

我不确定你认为什么是巨大的 - 你将在置换列表中得到N *(N + 1)/ 2个元素,以获得N个原始元素的唯一列表,所以这个变得非常快。

我做了一些基本的性能测试,使用Intel(Haswell)Core i7 @ 4GHz和32GB内存,运行Erlang / OTP 17 64位。

根据计时器,列表中的5001个元素占用2到5秒:tc / 1。

列表中的10001个元素需要15到17秒,并且需要大约9GB的内存。这将生成一个包含50,015,001个元素的列表。

列表中的15001个元素需要21到25秒,并且需要大约19GB的内存。

列表中的20001个元素在一次运行中耗时49秒,并且在大约30GB的内存中达到峰值,结果中有大约2亿个元素。这是我可以测试的极限。