アーランのエラトステネスのふるい[閉店]
-
02-07-2019 - |
質問
Erlangを学習中です。演習として、素数を生成する Sieve of Eratosthenes アルゴリズムを取り上げました。ここに私のコードがあります:
-module(seed2).
-export([get/1]).
get(N) -> WorkList = lists:duplicate(N, empty),
get(2, N, WorkList, []).
get(thats_the_end, _N, _WorkList, ResultList) -> lists:reverse(ResultList);
get(CurrentPrime, N, WorkList, ResultList) -> ModWorkList = markAsPrime(CurrentPrime, N, WorkList),
NextPrime = findNextPrime(CurrentPrime + 1, N, WorkList),
get(NextPrime, N, ModWorkList, [CurrentPrime|ResultList]).
markAsPrime(CurrentPrime, N, WorkList) when CurrentPrime =< N -> WorkListMod = replace(CurrentPrime, WorkList, prime),
markAllMultiples(CurrentPrime, N, 2*CurrentPrime, WorkListMod).
markAllMultiples(_ThePrime, N, TheCurentMark, WorkList) when TheCurentMark > N -> WorkList;
markAllMultiples(ThePrime, N, TheCurrentMark, WorkList) -> WorkListMod = replace(TheCurrentMark, WorkList, marked),
markAllMultiples(ThePrime, N, TheCurrentMark + ThePrime, WorkListMod).
findNextPrime(Iterator, N, _WorkList) when Iterator > N -> thats_the_end;
findNextPrime(Iterator, N, WorkList) -> I = lists:nth(Iterator, WorkList),
if
I =:= empty -> Iterator;
true -> findNextPrime(Iterator + 1, N, WorkList)
end.
replace(N, L, New)-> {L1, [_H|L2]} = lists:split(N - 1, L),
lists:append(L1, [New|L2]).
このコードは実際に動作します:)。問題は、それが最善の実装ではないというこの感覚を持っていることです。
私の質問は、「erlangish」とは何でしょうか。 「エラトステネスのふるい」を実装する方法
編集:OK、アンドレアスのソリューションは非常に優れていますが、遅いです。それを改善する方法はありますか?
解決
これは単純な(ただし、それほど高速ではない)Sieve実装です:
-module(primes).
-export([sieve/1]).
-include_lib("eunit/include/eunit.hrl").
sieve([]) ->
[];
sieve([H|T]) ->
List = lists:filter(fun(N) -> N rem H /= 0 end, T),
[H|sieve(List)];
sieve(N) ->
sieve(lists:seq(2,N)).
他のヒント
リスト内包表記を使用し、末尾再帰にしようとする私のsieve実装です。最後にリストを逆にして、素数がソートされるようにします:
primes(Prime, Max, Primes,Integers) when Prime > Max ->
lists:reverse([Prime|Primes]) ++ Integers;
primes(Prime, Max, Primes, Integers) ->
[NewPrime|NewIntegers] = [ X || X <- Integers, X rem Prime =/= 0 ],
primes(NewPrime, Max, [Prime|Primes], NewIntegers).
primes(N) ->
primes(2, round(math:sqrt(N)), [], lists:seq(3,N,2)). % skip odds
2GHz Macで最大2 milの素数を計算するのに約2.8ミリ秒かかります。
同時処理を使用して問題に取り組みました。
以前の投稿が正しくフォーマットされませんでした。これがコードの再投稿です。スパム送信して申し訳ありません...
-module(test).
%%-export([sum_primes/1]).
-compile(export_all).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%Sum of all primes below Max. Will use sieve of Eratosthenes
sum_primes(Max) ->
LastCheck = round(math:sqrt(Max)),
All = lists:seq(3, Max, 2), %note are creating odd-only array
%%Primes = sieve(noref,All, LastCheck),
Primes = spawn_sieve(All, LastCheck),
lists:sum(Primes) + 2. %adding back the number 2 to the list
%%sieve of Eratosthenes
sieve(Ref,All, LastCheck) ->
sieve(Ref,[], All, LastCheck).
sieve(noref,Primes, All = [Cur|_], LastCheck) when Cur > LastCheck ->
lists:reverse(Primes, All); %all known primes and all remaining from list (not sieved) are prime
sieve({Pid,Ref},Primes, All=[Cur|_], LastCheck) when Cur > LastCheck ->
Pid ! {Ref,lists:reverse(Primes, All)};
sieve(Ref,Primes, [Cur|All2], LastCheck) ->
%%All3 = lists:filter(fun(X) -> X rem Cur =/= 0 end, All2),
All3 = lists_filter(Cur,All2),
sieve(Ref,[Cur|Primes], All3, LastCheck).
lists_filter(Cur,All2) ->
lists_filter(Cur,All2,[]).
lists_filter(V,[H|T],L) ->
case H rem V of
0 ->
lists_filter(V,T,L);
_ ->
lists_filter(V,T,[H|L])
end;
lists_filter(_,[],L) ->
lists:reverse(L).
%% This is a sloppy implementation ;)
spawn_sieve(All,Last) ->
%% split the job
{L1,L2} = lists:split(round(length(All)/2),All),
Filters = filters(All,Last),
L3 = lists:append(Filters,L2),
Pid = self(),
Ref1=make_ref(),
Ref2=make_ref(),
erlang:spawn(?MODULE,sieve,[{Pid,Ref1},L1,Last]),
erlang:spawn(?MODULE,sieve,[{Pid,Ref2},L3,Last]),
Res1=receive
{Ref1,R1} ->
{1,R1};
{Ref2,R1} ->
{2,R1}
end,
Res2= receive
{Ref1,R2} ->
{1,R2};
{Ref2,R2} ->
{2,R2}
end,
apnd(Filters,Res1,Res2).
filters([H|T],Last) when H
[H|filters(T,Last)];
filters([H|_],_) ->
[H];
filters(_,_) ->
[].
apnd(Filters,{1,N1},{2,N2}) ->
lists:append(N1,subtract(N2,Filters));
apnd(Filters,{2,N2},{1,N1}) ->
lists:append(N1,subtract(N2,Filters)).
subtract([H|L],[H|T]) ->
subtract(L,T);
subtract(L=[A|_],[B|_]) when A > B ->
L;
subtract(L,[_|T]) ->
subtract(L,T);
subtract(L,[]) ->
L.
これらについては詳しく調査していませんが、以下の実装をテストしました(プロジェクトオイラーチャレンジ用に作成したもの)。上記の2つの実装よりも桁違いに高速です。いくつかのカスタム関数を削除し、代わりにリストを探すまで、それは耐え難いほど遅かった:同じことをする関数。レッスンを学習して、実行する必要のあるもののライブラリ実装があるかどうかを常に確認することをお勧めします。通常は高速です。これは、2.8GHz iMacで3.6秒で最大200万までの素数の合計を計算します...
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Sum of all primes below Max. Will use sieve of Eratosthenes
sum_primes(Max) ->
LastCheck = round(math:sqrt(Max)),
All = lists:seq(3, Max, 2), %note are creating odd-only array
Primes = sieve(All, Max, LastCheck),
%io:format("Primes: ~p~n", [Primes]),
lists:sum(Primes) + 2. %adding back the number 2 to the list
%sieve of Eratosthenes
sieve(All, Max, LastCheck) ->
sieve([], All, Max, LastCheck).
sieve(Primes, All, Max, LastCheck) ->
%swap the first element of All onto Primes
[Cur|All2] = All,
Primes2 = [Cur|Primes],
case Cur > LastCheck of
true ->
lists:append(Primes2, All2); %all known primes and all remaining from list (not sieved) are prime
false ->
All3 = lists:filter(fun(X) -> X rem Cur =/= 0 end, All2),
sieve(Primes2, All3, Max, LastCheck)
end.
私はこの主題、つまり素数が好きなので、BarryEのコードを少し変更し始めました。私は自分のlists_filter関数を作成して、両方のCPUを利用できるようにすることで、約70%速くなるようにしました。また、2つのバージョンに簡単に切り替えられるようにしました。テスト実行は次を示します:
61> timer:tc(test,sum_primes,[2000000]). {2458537,142913970581}
コード:
-module(test). %%-export([sum_primes/1]). -compile(export_all). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%Sum of all primes below Max. Will use sieve of Eratosthenes sum_primes(Max) -> LastCheck = round(math:sqrt(Max)), All = lists:seq(3, Max, 2), %note are creating odd-only array %%Primes = sieve(noref,All, LastCheck), Primes = spawn_sieve(All, LastCheck), lists:sum(Primes) + 2. %adding back the number 2 to the list %%sieve of Eratosthenes sieve(Ref,All, LastCheck) -> sieve(Ref,[], All, LastCheck). sieve(noref,Primes, All = [Cur|_], LastCheck) when Cur > LastCheck -> lists:reverse(Primes, All); %all known primes and all remaining from list (not sieved) are prime sieve({Pid,Ref},Primes, All=[Cur|_], LastCheck) when Cur > LastCheck -> Pid ! {Ref,lists:reverse(Primes, All)}; sieve(Ref,Primes, [Cur|All2], LastCheck) -> %%All3 = lists:filter(fun(X) -> X rem Cur =/= 0 end, All2), All3 = lists_filter(Cur,All2), sieve(Ref,[Cur|Primes], All3, LastCheck). lists_filter(Cur,All2) -> lists_filter(Cur,All2,[]). lists_filter(V,[H|T],L) -> case H rem V of 0 -> lists_filter(V,T,L); _ -> lists_filter(V,T,[H|L]) end; lists_filter(_,[],L) -> lists:reverse(L). %% This is a sloppy implementation ;) spawn_sieve(All,Last) -> %% split the job {L1,L2} = lists:split(round(length(All)/2),All), Filters = filters(All,Last), %%io:format("F:~p~n",[Filters]), L3 = lists:append(Filters,L2), %%io:format("L1:~w~n",[L1]), %% io:format("L2:~w~n",[L3]), %%lists_filter(Cur,All2,[]). Pid = self(), Ref1=make_ref(), Ref2=make_ref(), erlang:spawn(?MODULE,sieve,[{Pid,Ref1},L1,Last]), erlang:spawn(?MODULE,sieve,[{Pid,Ref2},L3,Last]), Res1=receive {Ref1,R1} -> {1,R1}; {Ref2,R1} -> {2,R1} end, Res2= receive {Ref1,R2} -> {1,R2}; {Ref2,R2} -> {2,R2} end, apnd(Filters,Res1,Res2). filters([H|T],Last) when H [H|filters(T,Last)]; filters([H|_],_) -> [H]; filters(_,_) -> []. apnd(Filters,{1,N1},{2,N2}) -> lists:append(N1,subtract(N2,Filters)); apnd(Filters,{2,N2},{1,N1}) -> lists:append(N1,subtract(N2,Filters)). subtract([H|L],[H|T]) -> subtract(L,T); subtract(L=[A|_],[B|_]) when A > B -> L; subtract(L,[_|T]) -> subtract(L,T); subtract(L,[]) -> L.
これを上司に見せることができます: http://www.sics.se/~ joe / apachevsyaws.html 。また、他の(クラシック?)アーラン引数は次のとおりです。
-ノンストップ操作。新しいコードをその場でロードできます。
-デバッグが簡単で、分析するコアダンプはもうありません。
-マルチコア/ CPUの利用が簡単
-クラスターを利用するのは簡単ですか?
-ポインタなどを処理したいのは誰ですか?これは21世紀ではありませんか? ;)
いくつかの落とし穴: -何かを書くのは簡単で速いように見えるかもしれませんが、パフォーマンスは悪くなります。もし私が 何かを高速にしたい私は通常、同じものの2-4の異なるバージョンを書くことになります 関数。そして、しばしばあなたは、かもしれない問題に鷹の目のアプローチを取る必要があります 使用されているものとは少し異なります。
-
リスト内の項目を検索&gt;約1000個の要素は遅いので、etsテーブルを使用してみてください。
-
文字列&quot; abc&quot; 3バイトよりも多くのスペースを必要とします。したがって、バイナリを使用してみてください(これは苦痛です)。
全体として、パフォーマンスの問題は、アーランで何かを書くときに常に留意すべきことだと思います。アーランの男たちはそれを解決する必要があり、彼らはそうするだろうと思う。
Erlangで素数を見つけるための4つの異なる実装(そのうち2つは「実際の」ふるいです)およびパフォーマンス測定結果を見つけるために、ここを見てください:
http:/ /caylespandon.blogspot.com/2009/01/in-euler-problem-10-we-are-asked-to.html
十分に単純で、アルゴリズムを正確に実装し、ライブラリ関数を使用しません(パターンマッチングとリスト内包のみ)。 それほど強力ではありません。できるだけシンプルにしようとしました。
-module(primes).
-export([primes/1, primes/2]).
primes(X) -> sieve(range(2, X)).
primes(X, Y) -> remove(primes(X), primes(Y)).
range(X, X) -> [X];
range(X, Y) -> [X | range(X + 1, Y)].
sieve([X]) -> [X];
sieve([H | T]) -> [H | sieve(remove([H * X || X <-[H | T]], T))].
remove(_, []) -> [];
remove([H | X], [H | Y]) -> remove(X, Y);
remove(X, [H | Y]) -> [H | remove(X, Y)].
これはエラトステネス実装のC&amp; Cのふるいです:
-module(sieve).
-export([find/2,mark/2,primes/1]).
primes(N) -> [2|lists:reverse(primes(lists:seq(2,N),2,[]))].
primes(_,0,[_|T]) -> T;
primes(L,P,Primes) -> NewList = mark(L,P),
NewP = find(NewList,P),
primes(NewList,NewP,[NewP|Primes]).
find([],_) -> 0;
find([H|_],P) when H > P -> H;
find([_|T],P) -> find(T,P).
mark(L,P) -> lists:reverse(mark(L,P,2,[])).
mark([],_,_,NewList) -> NewList;
mark([_|T],P,Counter,NewList) when Counter rem P =:= 0 -> mark(T,P,Counter+1,[P|NewList]);
mark([H|T],P,Counter,NewList) -> mark(T,P,Counter+1,[H|NewList]).
ここに私のサンプルがあります
S = lists:seq(2,100),
lists:foldl(fun(A,X) -> X--[A] end,S,[Y||X<-S,Y<-S,X<math:sqrt(Y)+1,Y rem X==0]).
:-)
これまでの最速のコード(アンドレアのコードよりも速い)は、配列を使用することです:
-module(seed4).
-export([get/1]).
get(N) -> WorkList = array:new([{size, N}, {default, empty}]),
get(2, N, WorkList, []).
get(thats_the_end, _N, _WorkList, ResultList) -> lists:reverse(ResultList);
get(CurrentPrime, N, WorkList, ResultList) -> ModWorkList = markAsPrime(CurrentPrime, N, WorkList),
NextPrime = findNextPrime(CurrentPrime + 1, N, WorkList),
get(NextPrime, N, ModWorkList, [CurrentPrime|ResultList]).
markAsPrime(CurrentPrime, N, WorkList) when CurrentPrime =< N -> WorkListMod = replace(CurrentPrime, WorkList, prime),
markAllMultiples(CurrentPrime, N, 2*CurrentPrime, WorkListMod).
markAllMultiples(_ThePrime, N, TheCurentMark, WorkList) when TheCurentMark > N -> WorkList;
markAllMultiples(ThePrime, N, TheCurrentMark, WorkList) -> WorkListMod = replace(TheCurrentMark, WorkList, marked),
markAllMultiples(ThePrime, N, TheCurrentMark + ThePrime, WorkListMod).
findNextPrime(Iterator, N, _WorkList) when Iterator > N -> thats_the_end;
findNextPrime(Iterator, N, WorkList) -> I = array:get(Iterator - 1, WorkList),
if
I =:= empty -> Iterator;
true -> findNextPrime(Iterator + 1, N, WorkList)
end.
replace(N, L, New) -> array:set(N - 1, New, L).