如何在Erlang中高效地从STDIN读取数千行?

如何在Erlang中高效地从STDIN读取数千行?,erlang,stdin,Erlang,Stdin,我在阅读STDIN的数千行文章时偶然发现了一个问题。这可能是一个假想的边缘情况,直到我发现一些测试需要从STDIN读取数千行。起初,我认为我的算法不是最优的,只是偶然发现,只有在不进行任何计算的情况下阅读行才能使测试时间缩短一半 以下是超时的部件代码: process_queries(0, _) -> ok; process_queries(N, A) -> case io:fread("", "~s~d~d") of {ok, _} -> proce

我在阅读STDIN的数千行文章时偶然发现了一个问题。这可能是一个假想的边缘情况,直到我发现一些测试需要从STDIN读取数千行。起初,我认为我的算法不是最优的,只是偶然发现,只有在不进行任何计算的情况下阅读行才能使测试时间缩短一半

以下是超时的部件代码:

process_queries(0, _) -> ok;
process_queries(N, A) -> 
    case io:fread("", "~s~d~d") of
        {ok, _} -> process_queries(N - 1, A)
        %{ok, ["Q", L, R]} -> process_queries(N - 1, apply_q(L, R, A));
        %{ok, ["U", Idx, Val]} -> process_queries(N - 1, apply_u(Idx, Val, A))
    end
.
我故意留下评论,表明所有的计算都被禁用了。因此,给定
N=7984
,此代码超时

有没有更好的方法来读取和处理Erlang中STDIN的数千行代码

  • io:get_line
    一次只获取一行
  • io:get_chars
    要求您知道要获取多少个字符

我建议将stdio切换为二进制,然后使用
io:get\u line
。通过在空格上拆分并将两个值转换为整数,您的数据格式非常容易解析。在一个简单的基准测试中,以下代码的运行速度大约是您的代码的10倍。我使用
escript
进行基准测试,这意味着,由于escript在运行中解析和编译代码,这种差异实际上很可能超过10倍

process_queries_2(0, _) -> ok;
process_queries_2(N, A) -> 
    Line = io:get_line(""),
    [X, Y0, Z0, _] = binary:split(Line, [<<$\s>>, <<$\n>>], [global]),
    Y = binary_to_integer(Y0),
    Z = binary_to_integer(Z0),
    % use X, Y, Z
    process_queries_2(N - 1, A).

加速的原因是Erlang字符串是链表,与二进制文件相比,它的CPU时间和内存使用效率都非常低,二进制文件是一个连续的内存块。

我建议将stdio切换为二进制,然后使用
io:get\u line
。通过在空格上拆分并将两个值转换为整数,您的数据格式非常容易解析。在一个简单的基准测试中,以下代码的运行速度大约是您的代码的10倍。我使用
escript
进行基准测试,这意味着,由于escript在运行中解析和编译代码,这种差异实际上很可能超过10倍

process_queries_2(0, _) -> ok;
process_queries_2(N, A) -> 
    Line = io:get_line(""),
    [X, Y0, Z0, _] = binary:split(Line, [<<$\s>>, <<$\n>>], [global]),
    Y = binary_to_integer(Y0),
    Z = binary_to_integer(Z0),
    % use X, Y, Z
    process_queries_2(N - 1, A).

加速的原因是Erlang字符串是链表,与二进制文件相比,它在CPU时间和内存使用方面都非常低效,二进制文件是一个连续的内存块。

我的解决方案中有一个摘录。如何真正有效地完成这项工作,几乎没有什么诀窍

read_command(CP) ->
    {ok, Line} = file:read_line(standard_io),
    [C, A, B] = binary:split(Line, CP, [global, trim_all]),
    {case C of <<"Q">> -> 'Q'; <<"U">> -> 'U' end,
     binary_to_integer(A),
     binary_to_integer(B)}.

read_commands(N, CP) ->
    [ read_command(CP) || _ <- lists:seq(1, N) ].

execute(Array, L) ->
    lists:foldl(fun({'Q', F, T}, A) ->
                        {Val, A2} = query(A, F, T),
                        file:write(standard_io, [integer_to_binary(Val), $\n]),
                        A2;
                   ({'U', I, V}, A) ->
                        update(A, I, V)
                end, Array, L).

read_int_line(CP) ->
    {ok, Line} = file:read_line(standard_io),
    [binary_to_integer(X) || X <- binary:split(Line, CP, [global, trim_all])].

main() ->
    ok = io:setopts([binary]),
    CP = binary:compile_pattern([<<" ">>, <<$\n>>]),
    [N] = read_int_line(CP),
    L = read_int_line(CP),
    N = length(L),
    [K] = read_int_line(CP),
    execute(init(L), read_commands(K, CP)).
read_命令(CP)->
{ok,Line}=文件:读取行(标准io),
[C,A,B]=二进制:拆分(行,CP,[global,trim_all]),
{案例C的->'Q';案例C的->'U'端,
二进制_到_整数(A),
二进制_到_整数(B)}。
读取\u命令(N,CP)->
[读取命令(CP)| |
列表:foldl(fun({'Q',F,T},A)->
{Val,A2}=query(A,F,T),
文件:写入(标准io,[整数到二进制(Val),$\n]),
A2;
({'U',I,V},A)->
更新(A、I、V)
结束,数组,L)。
读取内部线(CP)->
{ok,Line}=文件:读取行(标准io),
[二进制_到_整数(X)| | X
ok=io:setopts([二进制]),
CP=binary:compile_模式([,]),
[N] =读取内部线(CP),
L=读取线(CP),
N=长度(L),
[K] =读取内部线(CP),
执行(init(L),read_命令(K,CP))。

当然,您必须编写自己的
init/1
update/3
query/3

这里有一段摘录自我的解决方案。如何真正有效地完成这项工作,没有什么诀窍

read_command(CP) ->
    {ok, Line} = file:read_line(standard_io),
    [C, A, B] = binary:split(Line, CP, [global, trim_all]),
    {case C of <<"Q">> -> 'Q'; <<"U">> -> 'U' end,
     binary_to_integer(A),
     binary_to_integer(B)}.

read_commands(N, CP) ->
    [ read_command(CP) || _ <- lists:seq(1, N) ].

execute(Array, L) ->
    lists:foldl(fun({'Q', F, T}, A) ->
                        {Val, A2} = query(A, F, T),
                        file:write(standard_io, [integer_to_binary(Val), $\n]),
                        A2;
                   ({'U', I, V}, A) ->
                        update(A, I, V)
                end, Array, L).

read_int_line(CP) ->
    {ok, Line} = file:read_line(standard_io),
    [binary_to_integer(X) || X <- binary:split(Line, CP, [global, trim_all])].

main() ->
    ok = io:setopts([binary]),
    CP = binary:compile_pattern([<<" ">>, <<$\n>>]),
    [N] = read_int_line(CP),
    L = read_int_line(CP),
    N = length(L),
    [K] = read_int_line(CP),
    execute(init(L), read_commands(K, CP)).
read_命令(CP)->
{ok,Line}=文件:读取行(标准io),
[C,A,B]=二进制:拆分(行,CP,[global,trim_all]),
{案例C的->'Q';案例C的->'U'端,
二进制_到_整数(A),
二进制_到_整数(B)}。
读取\u命令(N,CP)->
[读取命令(CP)| |
列表:foldl(fun({'Q',F,T},A)->
{Val,A2}=query(A,F,T),
文件:写入(标准io,[整数到二进制(Val),$\n]),
A2;
({'U',I,V},A)->
更新(A、I、V)
结束,数组,L)。
读取内部线(CP)->
{ok,Line}=文件:读取行(标准io),
[二进制_到_整数(X)| | X
ok=io:setopts([二进制]),
CP=binary:compile_模式([,]),
[N] =读取内部线(CP),
L=读取线(CP),
N=长度(L),
[K] =读取内部线(CP),
执行(init(L),read_命令(K,CP))。

当然,你必须编写自己的
init/1
update/3
query/3

我怀疑在Erlang问题的实际世界中不存在的与黑客银行相关的问题是否会引起很大的反应。如果你在这里没有得到答案,试试IRC,或者现实世界中的问题。我怀疑黑客银行在Erlang问题的实际世界中不存在的相关问题会激发很多响应。如果您在这里没有得到响应,请尝试IRC或现实世界的问题。谢谢,我有几个问题。为什么需要
init/1
函数?看起来整个数组已经在
L
中了。@m3nthal:因为我正在使用g不同的内部表示法。
L
是一个列表,而不是数组。它的访问特性不好。我还对数字使用了不同的表示法,以便更快地计算最小公共乘数。我做了一些研究,发现dicts和map的访问和随机更新速度更快。您如何表示数字?二进制格式?可能您也在使用二进制gcd算法?@m3nthal:我将数字表示为因子的有序列表。然后您可以将gcd表示为交集,将lcm表示为并集。我使用合并联接来表示。对于表示数组,我使用了与模块中使用的类似的N树结构,但已重新实现。(我在
maps
变得普通之前编写了解决方案。)谢谢,我有几个问题。为什么需要
init/1
函数?看起来整个数组已经在
L
中了。@m3nthal:因为我使用的是不同的内部表示。
L
是一个列表,而不是一个数组。它有一些不好的特性