erlang #1 素数リストを求める

急に思い立ってerlang素数リストを求めるプログラムを書いた。

エラトステネスのふるい

まずは基本的なプログラミング。関数prime()は再帰的にsqrt(N)回実行する。

# cat prime1.erl
-module(prime1).
-export([getl/1, checkp/1]).

%% Nまでの素数リストを返す
getl(1) -> [];
getl(2) -> [2];
getl(N) -> prime(trunc(math:sqrt(N)), [2], lists:seq(3,N,2)).

%% prime(Max:最大実行回数、P:素数リスト、[H|T]:3からNまでの奇数のリスト)
prime(Max, P, [H|T]) ->
        if
          H > Max ->
                P ++ [H|T];
          true ->
               prime(Max, P ++ [H],  lists:filter(fun(X) -> (X rem H) =/= 0 end,  T)) 
         end.

%% Nが素数かどうかチェック
checkp(N) -> (lists:last(getl(N)) =:= N).

実行は以下のとおり。

# erl
Erlang R16B02 (erts-5.10.3) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> c(prime1).
{ok,prime1}
2> prime1:getl(10).
[2,3,5,7]
3> prime1:getl(20).
[2,3,5,7,11,13,17,19]
4> prime1:getl(100).
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,
 79,83,89,97]
5> prime1:checkp(97).
true
6> prime1:checkp(99).
false

サーバ化

次、サーバ化する。

# cat prime2.erl

-module(prime2).
-export([start/0, check/2, get/2]).

%%
%% クライアント
%%
get(Pid, N) -> rpc(Pid, {get, N}).
check(Pid, N) -> rpc(Pid, {check, N}).

rpc(Pid, {Type, Val}) ->
         Pid ! {self(), {Type, Val}},
         receive
                Responce -> Responce
        end.

%%
%% サーバ
%%
start() -> spawn(fun loop/0).

loop() ->
       receive
                {FROM, {get, N}} -> FROM ! getl(N), loop();
                {FROM, {check,N}} -> FROM ! checkp(N), loop()
        end.

%% Nまでの素数リストを返す
getl(1) -> [];
getl(2) -> [2];
getl(N) -> prime(trunc(math:sqrt(N)), [2], lists:seq(3,N,2)).

%% prime(Max:最大実行回数、P:素数リスト、[H|T]:3からNまでの奇数のリスト)
prime(Max, P, [H|T]) ->
        if
          H > Max ->
                P ++ [H|T];
          true ->
               prime(Max, P ++ [H],lists:filter(fun(X) -> (X rem H) =/= 0 end,  T))
         end.

%% Nが素数かどうかチェック
checkp(N) -> (lists:last(getl(N)) =:= N).

実行例。最後に1000000までの素数リストを計算させる。数10秒くらいかかる。

# erl
Erlang R16B02 (erts-5.10.3) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> c(prime2).
{ok,prime2}
2> Pid = prime2:start().
<0.40.0>
3> prime2:get(Pid, 10).
[2,3,5,7]
4> prime2:get(Pid, 20). 
[2,3,5,7,11,13,17,19]
5> prime2:check(Pid, 20).
false
6> prime2:check(Pid, 19).
true
7>  prime2:get(Pid, 10000000).
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,
 79,83,89,97,101,103,107,109|...]
8> length(prime2:get(Pid, 10000000)).
664579

サーバにキャッシュ機能をいれる

キャッシュ化の元ネタはこちら
ただし、こちらはfoldlを使って2からNまでチェックする。
他方、ここで書いたふるいの方法はprime()の再帰呼び出しでsqrt(N)回のチェックで済ませる。よって、実行時間は相当短縮できている。

# cat prime3.erl

-module(prime3).
-export([start/0, check/2, get/2]).

%%
%% クライアント
%%
get(Pid, N) -> rpc(Pid, {get, N}).
check(Pid, N) -> rpc(Pid, {check, N}).

rpc(Pid, {Type, Val}) ->
         Pid ! {self(), {Type, Val}},
         receive
                Responce -> Responce
        end.

%%
%% キャッシュ機能付きサーバ
%%

start() -> spawn(fun() -> loop(2,[2]) end).

%% L := Checkedまでの素数リスト
loop(Checked, L) ->
       receive
        {FROM, {get, N}} ->
                Ret = getl(N, Checked, L),
                FROM ! Ret;
        {FROM, {check, N}} ->
               Ret = getl(N, Checked, L),
               %% 関数checkp()は廃止、素数リストの末尾を比較
               FROM ! (lists:last(Ret) =:= N)
        end,
        loop(max(Checked, N), (if (length(Ret) < length(L)) -> L;
                                 true -> Ret end)).

%% Nまでの素数リストを返す
getl(1, _, _) -> [];
getl(N, Checked, L) ->
             if
                N =< Checked -> %% すでに求めた素数リストLから返す
                        lists:filter(fun(X) -> X =< N end, L);
                true -> %% すでに求めた素数リストに、Nまでの整数リストを追加
                     prime(trunc(math:sqrt(N)), [], L ++ lists:seq(Checked,N))
        end.

%% prime(Max:最大実行回数、P:素数リスト、[H|T]:2からNまでの整数のリスト)
prime(Max, P, [H|T]) ->
        if
          H > Max ->
                P ++ [H|T];
          true ->
               prime(Max, P ++ [H],lists:filter(fun(X) -> (X rem H) =/= 0 end,  T))
         end.


実行方法は上と同じ。大きな数値を入力すると、最初は時間がかかる。
しかし次に実行するとキャッシュされたリストを即、返す。

# erl
Erlang R16B02 (erts-5.10.3) [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> c(prime3).
{ok,prime3}
2> Pid = prime3:start().
<0.34.0>
3> prime3:get(Pid, 10000000).
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,
 79,83,89,97,101,103,107,109|...]
4> prime3:get(Pid, 10000000).
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,
 79,83,89,97,101,103,107,109|...]
5> length(prime3:get(Pid, 10000000)).
664579
6> length(prime3:get(Pid, 1200000)). 
92938
7> length(prime3:get(Pid, 12000000)).
788060