为什么在Erlang/OTP20上评估此列表需要太长时间?

为什么在Erlang/OTP20上评估此列表需要太长时间?,erlang,Erlang,查找总和为100的任意5个数字。这可以在一个循环中完成,但我向一个朋友演示了列表理解,却发现在我的MacBookPro上,CoreI7,2.2GHz,这需要30多分钟 [[A,B,C,D,E]| | AErlang strong当我们在编程中使用并发时,您还可以生成100个进程来处理[1,…,100]的列表。您的笔记本电脑可以很容易地进行计算。例如: do()-> L100 = lists:seq(1,100), [spawn(?MODULE, func, [sel

查找总和为100的任意5个数字。这可以在一个循环中完成,但我向一个朋友演示了列表理解,却发现在我的MacBookPro上,CoreI7,2.2GHz,这需要30多分钟


[[A,B,C,D,E]| | AErlang strong当我们在编程中使用并发时,您还可以生成100个进程来处理[1,…,100]的列表。您的笔记本电脑可以很容易地进行计算。例如:

do()->    
    L100 = lists:seq(1,100),
    [spawn(?MODULE, func, [self(), [A], L100, L100, L100, L100]) || 
        A <- L100],    
    loop(100, []).
loop(0, Acc) -> Acc;
loop(N, Acc) ->
    receive
        {ok, Result} ->
            loop(N - 1, Acc ++ Result)
    end.

func(Pid, LA, LB, LC, LD, LE) ->
    Result = [[A,B,C,D,E] ||
             A <- LA,B <- LB,C <- LC,D <- LD,E <- LE,(A + B + C + D + E) == 100],
    Pid ! {ok, Result}.
do()->
L100=列表:序号(1100),
[spawn(?MODULE,func,[self(),[A],L100,L100,L100,L100])|
A行政协调会;
环路(N,Acc)->
接收
{好的,结果}->
循环(N-1,Acc++结果)
结束。
函数(Pid、LA、LB、LC、LD、LE)->
结果=[[A,B,C,D,E]||
A因为它在生成过滤器之前“创建”了100^5个元素列表中的所有元素,该列表包含5个元素,表示50000000000个元素

[编辑] 我回顾了RichardC和Alexey Romanov的答案,决定做一些测试:

-module (testlc).

-export ([test/1]).

test(N) ->
    F1 = fun() -> [{W,X,Y,Z}|| W <- lists:seq(1,N),X <- lists:seq(1,N),Y <- lists:seq(1,N),Z <- lists:seq(1,N), W+X+Y+Z == N] end,
    F2 = fun() ->L = lists:seq(1,N),  [{W,X,Y,Z}|| W <- L,X <- L,Y <- L,Z <- L, W+X+Y+Z == N] end,
    F3 = fun() -> [{W,X,Y,Z}|| W <- lists:seq(1,N-3), X <- lists:seq(1,N-2-W),Y <- lists:seq(1,N-1-W-X),Z <- lists:seq(1,N-W-X-Y), W+X+Y+Z == N] end,
    F4 = fun() -> [{W,X,Y,N-W-X-Y}|| W <- lists:seq(1,N-3),X <- lists:seq(1,N-2-W),Y <- lists:seq(1,N-1-W-X)] end,
    F5 = fun() -> L = lists:seq(1,N), [{W,X,Y,N-W-X-Y}|| W <- L, 
                                                         XM <- [N-2-W],      X <- L, X =< XM, 
                                                         YM <- [N-1-W-X],    Y <- L, Y =< YM] end,
    {T1,L1} = timer:tc(F1),
    {T2,L2} = timer:tc(F2),
    {T3,L3} = timer:tc(F3),
    {T4,L4} = timer:tc(F4),
    {T5,L5} = timer:tc(F5),
    _L = lists:sort(L1),
    _L = lists:sort(L2),
    _L = lists:sort(L3),
    _L = lists:sort(L4),
    _L = lists:sort(L5),
    {test_for,N,{t1,T1},{t2,T2},{t3,T3},{t4,T4},{t5,T5}}.
在列表理解之外准备列表会产生很大的影响,但是在过滤器工作之前大幅限制生成的无用中间列表的数量会更有效。因此,这是一个需要评估的平衡。在本例中,这两个增强功能可以一起使用(感谢Alexey)但这并没有太大区别。

一个选择是

[[A,B,C,D,100-A-B-C-D] || A <- lists:seq(1,100), B <- lists:seq(1,100-A), C <- lists:seq(1,100-A-B), D <- lists:seq(1,100-A-B-C), 100-A-B-C-D > 0]
或者避免重复的
列表:seq
调用,正如@RichardC所指出的:

L = lists:seq(1, 100),
[[A,B,C,D,E] || A <- L, 
    BMax <- [100-A], B <- L, B =< BMax,
    CMax <- [BMax-B], C <- L, C =< CMax,
    DMax <- [CMax-C], D <- L, D =< DMax, 
    E <- [100-A-B-C-D], E > 0]
L=list:seq(1100),

[[A,B,C,D,E]| | A多个生成器的行为类似于列表上的嵌套循环,每个对列表的调用:seq()每次都将被完全计算。这需要很长的时间,大部分时间用于分配列表单元格并再次对其进行垃圾收集。但由于它们的计算结果都是相同的常量列表,您可以将其重写为L=lists:seq(1100),[[a,B,C,D,E]| | a,但需要注意的是,中间列表本身并不是实际创建的(我相信)。@AlexeyRomanov它确实创建了所有列表,但不是所有元素的单一列表;请看我的答案。我知道,这就是我为什么写“创建”。我的目的是提醒大家,尽管语法非常简洁,但它会在后台生成您描述的所有循环。因此,在非关键流程中,请谨慎使用多个生成器。为了好玩,还可以通过减少组合的数量来减少执行时间:
[[A,B,C,D,100-A-B-C-D]| | A睡前速度太快,一个好的版本是
[[A,B,C,D,E]| | A我认为这是我们问题的最佳答案,+1:)
[[A,B,C,D,E] || A <- lists:seq(1,100), 
    BMax <- [100-A], B <- lists:seq(1,BMax), 
    CMax <- [BMax-B], C <- lists:seq(1,CMax), 
    DMax <- [CMax-C], D <- lists:seq(1,DMax), 
    E <- [100-A-B-C-D], E > 0]
L = lists:seq(1, 100),
[[A,B,C,D,E] || A <- L, 
    BMax <- [100-A], B <- L, B =< BMax,
    CMax <- [BMax-B], C <- L, C =< CMax,
    DMax <- [CMax-C], D <- L, D =< DMax, 
    E <- [100-A-B-C-D], E > 0]