试图为Erlang中的Project Euler#14找到更快的解决方案

时间:2012-09-26 10:40:46

标签: erlang

我试图为Project Euler的问题14编写一个解决方案。我的最快 - 不是下面的那个 - 在58秒左右跑了。我发现使用谷歌的最快速度看起来或多或少是这样的:

%% ets:delete(collatz) (from shell) deletes the table.

-module(euler) .
-export([problem_14/1]) .

collatz(X) ->
  case ets:lookup(collatz, X) of
    [{X, Val}] -> Val ;
    []         -> case X rem 2 == 0 of
                    true  ->
                      ets:insert(collatz, {X, Val = 1+collatz(X div 2)} ) ,
                      Val ;
                    false ->
                      ets:insert(collatz, {X, Val = 1+collatz(3*X+1)} ) ,
                      Val
                  end
  end .

%% takes 10 seconds for N=1000000 on my netbook after "ets:delete(collatz)".
problem_14(N) ->
  case ets:info(collatz) of
    undefined ->
      ets:new(collatz, [public, named_table]) ,
      ets:insert(collatz,{1,1}) ;
    _         -> ok
  end ,
  lists:max([ {collatz(X), X} || X <- lists:seq(1,N) ]) .

但是空表还需要10.5秒。我发现C ++中最快的解决方案只花了0.18秒,快了58倍。所以我想即使Erlang不是为那些东西制作的,也可以写出更好的代码。有没有人知道我可以尝试获得一些速度?

2 个答案:

答案 0 :(得分:1)

我加快了你的代码:指定ets为ordered_set,使用按位运算并实现尾递归函数max_size_index,而不是将所有结果收集到列表中,然后迭代它找到最大值(如我们的代码所示)。

-module(collatz).
-compile(export_all).

size(1, _) ->
        1;
size(N, Hashset) ->
        case ets:lookup(Hashset, N) of
                [{N, Size}] ->
                        Size;
                [] ->
                        Size  = 1 + size( next(N), Hashset ),
                        ets:insert(Hashset, {N, Size}),
                        Size
        end.

next(N) when N band 1 == 0 ->
        N bsr 1;
next(N) ->
        (N bsl 1)+N+1.

max_size_index(1, _Hashset, {Index, MaxSize}) ->
        {Index, MaxSize};
max_size_index(N, Hashset, {Index, MaxSize}) ->
        CurrSize = size(N, Hashset),
        case CurrSize > MaxSize of
                true ->
                        max_size_index(N-1, Hashset, {N, CurrSize});
                false ->
                        max_size_index(N-1, Hashset, {Index, MaxSize})
        end.

problem_14(N) ->
        Hashset = ets:new(collatz_count, [public, ordered_set]),
        max_size_index(N, Hashset, {1,1}).

在shell中测试 - 您的模块euler和我的模块collatz()

1> c(euler).
{ok,euler}
2> 
2> timer:tc(euler, problem_14, [1000000]). 
{4039838,{525,837799}}
3> 
3> c(collatz).
{ok,collatz}
4> 
4> timer:tc(collatz, problem_14, [1000000]). 
{2824109,{837799,525}}

加速的最后一个提示 - 大间隔可以分成较小的,并且每个小间隔的并行生成计算(在其他节点上)。

答案 1 :(得分:0)

通常,原始CPU绑定操作不是Erlang的强项。正如您所注意到的,问题是数据被复制到ETS表和从ETS表复制。中央ETS表还有一个优点,它还可以锁定:原子更新。因此,如果需要,您可以轻松获得更多内核来解决问题。但是,你不会接近C ++或C解决方案的速度。

你遇到的这类问题的另一个问题是可变性。 Erlang在其(顺序)核心中具有(几乎)纯函数语言。所以你不能希望用一个短暂的哈希表或一个数组来打败C ++解决方案,它可以存储它运行的百万个条目。您可以尝试array模块,但我怀疑它会更快。