Я изучаю Erlang. В качестве упражнения я взял алгоритм генерации простых чисел Сито Эратосфена. Вот мой код:
-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]).
Этот код действительно работает :). Проблема в том, что у меня такое ощущение, что это не лучшая реализация.
Мой вопрос в том, каким будет «эрлангский» способ реализации «Решета Эратосфена».
Обновлено: Хорошо, решение Андреаса очень хорошее, но медленное. Есть идеи, как это улучшить?
Взгляните на codereview.stackexchange.com/questions/118204/….





Я подошел к проблеме, используя параллельную обработку.
Спасибо за быстрый ответ! Я демонстративно прочитаю ваш пост. Мой вопрос на самом деле не связан с параллелизмом, а просто старым последовательным программированием. Код получается слишком длинным по сравнению с реализацией C, так что, может быть, есть лучший способ?
имхо, ваш код вполне разумный. Цель вашего вопроса, казалось, была в том, как я могу сделать это более эрланговым. Параллелизм - это то место, где силен язык. Вот почему я указал вам на свой.
Получение параллелизма при работе с ситом - это небольшая проблема, поскольку данные не используются совместно между подпроцессами. Это все усложняет. Это одна из причин, по которой я не использовал этот алгоритм. Вы также должны взглянуть на en.wikipedia.org/wiki/Sieve_of_Atkin для другого взгляда на вещи.
EvilTeach, я проголосовал за ваш ответ, потому что он хороший. Я все еще не могу принять это как окончательный ответ. Если мой код разумный, я очень разочарован функциональным программированием в целом. Этот простой алгоритм намного проще реализовать на C.
В порядке. У меня нет проблем с этим. Вот тогда настоящий вопрос. Как можно использовать параллелизм для работы сита?
Вот простая (но не очень быстрая) реализация сита:
-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)).
Вау, это именно то, что я искал. Это намного проще и быстрее, чем мое решение. Спасибо !!! Только один вопрос, что такое -include_lib ("unit / include / eunit.hrl"). строка о?
О, это образец шаблона, который последовал. Это для использования EUnit, библиотеки тестирования модулей Erlang. Рад, что вы нашли код полезным :)
Обновлено: Хорошо, решение Андреаса очень хорошее, но медленное. Есть идеи, как это улучшить?
Роското, посмотри мой ответ. Этот код ответа, каким бы красивым он ни был, не является Решетом Эратосфена.
@DariusBacon, почему ты так говоришь?
См. Ссылку в моем ответе. Временная сложность этого кода намного хуже, чем у сита.
@Agis Haskellwiki's страница простых чисел обсуждает это в (несколько чрезмерной) длине. бумага, связанный в теперь удаленном ответе, упомянутом выше, выражает то же самое в математике (а не в словах): сито без отложенного пробного деления (как в этом ответе) является квадратичным. сито пробного деления перенесенный ~ n ^ 1.5. правильное сито Эратосфена - ~ n ^ {1.1..1.05} с массивами или ~ n ^ 1.2 с древовидные списки.
мой самый быстрый код (быстрее, чем у Андреа) с использованием массива:
-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).
Я не изучал их подробно, но я протестировал свою реализацию ниже (которую я написал для задачи Project Euler), и она на порядки быстрее, чем две вышеупомянутые реализации. Это было мучительно медленно, пока я не удалил некоторые пользовательские функции и вместо этого начал искать списки: функции, которые будут делать то же самое. Хорошо усвоить урок, чтобы всегда видеть, есть ли в библиотеке реализация того, что вам нужно сделать - обычно это будет быстрее! Это вычисляет сумму простых чисел до 2 миллионов за 3,6 секунды на iMac 2,8 ГГц ...
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%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.
Исправление: при тестировании на моей машине моя реализация была на несколько порядков быстрее, чем у Альнитака, но менее чем в 3 раза быстрее, чем у Роското. Различия между ними становятся заметными только при цифрах более 100К или около того.
Критикуя свой собственный код, я смешал функции сита в сумму. Начнется более чистое сито: sieve (Max) -> All = lists: seq (3, Max, 2), LastCheck = round (math: sqrt (Max)), sieve ([], All, Max, LastCheck).
Привет, BarryE, Спасибо за интерес к этому вопросу и его решению. Заглянув в ваш код, я нахожу идею Андреаса со списками: filter (rem). Имейте в виду, что rem очень медленная работа, кроме того, она не является частью исходного алгоритма, который использует суммирование.
Что делает ваш код быстрее, так это вещь LastCheck = round (math: sqrt (Max)), которая при правильном использовании в решении для массива сделает его самым быстрым решением.
Мне вроде как нравится эта тема, то есть простые числа, поэтому я начал немного модифицировать код BarryE, и мне удалось сделать его примерно на 70% быстрее, создав мою собственную функцию lists_filter и позволив использовать оба моих процессора. Я также упростил переключение между двумя версиями. Тестовый запуск показывает:
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.
Мой предыдущий пост не был правильно отформатирован. Вот репост кода. Извините за рассылку спама ...
-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.
вы можете показать своему боссу это: http://www.sics.se/~joe/apachevsyaws.html. И некоторые другие (классические?) Аргументы erlang:
-постоянная работа, новый код может быть загружен на лету.
- Легко отлаживать, больше не нужно анализировать дампы ядра.
-Простота использования многоядерных процессоров / процессоров
- может быть, легко использовать кластеры?
-кто хочет заниматься указателями и прочим? Разве это не 21 век? ;)
Некоторые пифоллы: - Может показаться, что написать что-то легко и быстро, но производительность может быть отстой. Если я хочу сделать что-то быстро, я обычно пишу 2-4 разных версии одного и того же функция. И часто вам нужно пристально подходить к проблемам, которые могут быть немного отличается от того, что используется.
поиск вещей в списках> около 1000 элементов выполняется медленно, попробуйте использовать таблицы ets.
строка «abc» занимает намного больше места, чем 3 байта. Так что попробуйте использовать двоичные файлы (а это боль).
В общем, я думаю, что проблема производительности - это то, о чем нужно всегда помнить, когда пишете что-то на erlang. Чуваки из Erlang должны решить это, и я думаю, что они это сделают.
Посмотрите здесь, чтобы найти 4 различных реализации для поиска простых чисел в Erlang (два из которых являются «настоящими» ситами) и для результатов измерения производительности:
http://caylespandon.blogspot.com/2009/01/in-euler-problem-10-we-are-asked-to.html
Вот моя реализация сита, которая использует понимание списков и пытается быть хвостовой рекурсивной. Я переворачиваю список в конце, чтобы отсортировать простые числа:
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
Для вычисления простых чисел до 2 мил на моем Mac с частотой 2 ГГц требуется около 2,8 мс.
Я работаю через эйлеров в эрланге. Я собираюсь украсть эту какашку и изучить ее. В этой реализации значительно меньше накладных расходов.
То же самое. Я не против позаимствовать решение, когда все, что мне действительно нужно, это понимание языка (а мои собственные решения не работают).
Достаточно простой, реализует в точности алгоритм и не использует библиотечных функций (только сопоставление с образцом и понимание списка). Действительно, не очень мощный. Я только старался сделать это как можно проще.
-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&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]).
:-)
Это не дает ответа на вопрос. Чтобы критиковать или запрашивать разъяснения у автора, оставьте комментарий под его сообщением.
@sethvargo, как это бывает на самом деле, этот делает отвечает на вопрос. Этот вопрос датируется 2008 годом, когда «покажи мне лучшее» было по теме, и это ответ на вопрос. Вопрос больше не по теме и должен быть закрыт.
Этот вопрос кажется не по теме, потому что он принадлежит codereview.stackexchange.com