Erlang fizzbuzzery
Herein, a few different ways of implementing "a" solution to the FizzBuzz problem, showing examples of some different programming styles in Erlang solving the same problem.
Using a loop:
-module(fizzbuzz_with_loop). -export([start/0]). start() -> fizzbuzz(1,100). fizzbuzz(N,M) when N > M -> noop; fizzbuzz(N,M) -> io:format("~s~n",[fizzbuzz(N)]), fizzbuzz(N+1,M). fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 -> "FizzBuzz"; fizzbuzz(N) when N rem 3 == 0 -> "Fizz"; fizzbuzz(N) when N rem 5 == 0 -> "Buzz"; fizzbuzz(N) -> integer_to_list(N).
Compile the module with
erlc fizzbuzz_loop.erl
and run with erl -s fizzbuzz_loop -run init stop -noshell
[1] to produce: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 ...
Using foreach (foreach is used because the order of evaluation is important):
-module(fizzbuzz_with_foreach). -export([start/0]). start() -> lists:foreach(fun(X) -> io:format("~s~n",[fizzbuzz(X)]) end, lists:seq(1,100)). fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 -> "FizzBuzz"; fizzbuzz(N) when N rem 3 == 0 -> "Fizz"; fizzbuzz(N) when N rem 5 == 0 -> "Buzz"; fizzbuzz(N) -> integer_to_list(N).
Using list comprehension:
-module(fizzbuzz_with_list_comprehension). -export([start/0]). start() -> lists:foreach(fun(Y) -> io:format("~s~n",[Y]) end, [fizzbuzz(X) || X <- lists:seq(1,100)]). fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 -> "FizzBuzz"; fizzbuzz(N) when N rem 3 == 0 -> "Fizz"; fizzbuzz(N) when N rem 5 == 0 -> "Buzz"; fizzbuzz(N) -> integer_to_list(N).
Using processes:
-module(fizzbuzz_with_processes). % export default start function for command line -export([start/0]). % export entry point for spawned processes -export([fizzbuzz_process/0]). start() -> Fizz_PID = spawn(fizzbuzz_with_processes,fizzbuzz_process,[]), lists:foreach(fun(X) -> ask_fizzbuzz(Fizz_PID,X) end, lists:seq(1,100)), Fizz_PID ! {done}. ask_fizzbuzz(Fizz_PID,N) -> Fizz_PID ! {fizzbuzz,N,self()}, receive {Answer} -> io:format("~s~n",[Answer]) end. fizzbuzz_process() -> receive {fizzbuzz,N,Caller_PID} -> Caller_PID ! {fizzbuzz(N)}, fizzbuzz_process(); {done} -> noop end. fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 -> "FizzBuzz"; fizzbuzz(N) when N rem 3 == 0 -> "Fizz"; fizzbuzz(N) when N rem 5 == 0 -> "Buzz"; fizzbuzz(N) -> integer_to_list(N).
With too many processes:
-module(fizzbuzz_with_too_many_processes). % export default start function for command line -export([start/0]). % export entry point for spawned processes -export([fizzbuzz_fizzer/1]). -export([fizzbuzz_buzzer/1]). -export([fizzbuzz_number/1]). -export([fizzbuzz_printer/0]). start() -> Printer = spawn(fizzbuzz_with_too_many_processes, fizzbuzz_printer, []), Processes = lists:map(fun(F) -> spawn(fizzbuzz_with_too_many_processes, F, [Printer]) end, [fizzbuzz_fizzer,fizzbuzz_buzzer,fizzbuzz_number]), lists:foreach(fun(X) -> lists:foreach(fun(P) -> P ! {n,X,self()}, receive {ok} -> noop end end, Processes), Printer ! {newline} end, lists:seq(1,100)), lists:foreach(fun(P) -> P ! {done} end, [Printer|Processes]). fizzbuzz_fizzer(Printer) -> receive {n,N,P} -> if N rem 3 == 0 -> Printer ! {s,"Fizz"}; true -> noop end, P ! {ok}, fizzbuzz_fizzer(Printer); {done} -> noop end. fizzbuzz_buzzer(Printer) -> receive {n,N,P} -> if N rem 5 == 0 -> Printer ! {s,"Buzz"}; true -> noop end, P ! {ok}, fizzbuzz_buzzer(Printer); {done} -> noop end. fizzbuzz_number(Printer) -> receive {n,N,P} -> if (N rem 5 =/= 0) and (N rem 3 =/= 0) -> Printer ! {n,N}; true -> noop end, P ! {ok}, fizzbuzz_number(Printer); {done} -> noop end. fizzbuzz_printer() -> receive {s,S} -> io:format("~s",[S]), fizzbuzz_printer(); {n,N} -> io:format("~w",[N]), fizzbuzz_printer(); {newline} -> io:format("~n",[]), fizzbuzz_printer(); {done} -> noop end.
Why is the above so painful? Mostly because we have to wait for a reply from each fizzbuzz_X process because otherwise some of the processes will send messages to the printer out of order. To compare, see what the output is if you did not wait for an
{ok}
: -module(fizzbuzz_with_asynch_processes). % export default start function for command line -export([start/0]). % export entry point for spawned processes -export([fizzbuzz_fizzer/1]). -export([fizzbuzz_buzzer/1]). -export([fizzbuzz_number/1]). -export([fizzbuzz_printer/0]). start() -> Printer = spawn(fizzbuzz_with_asynch_processes, fizzbuzz_printer, []), Processes = lists:map(fun(P) -> spawn(fizzbuzz_with_asynch_processes, P, [Printer]) end, [fizzbuzz_fizzer,fizzbuzz_buzzer,fizzbuzz_number]), lists:foreach(fun(N) -> lists:foreach(fun(P) -> P ! {n,N} end, Processes), Printer ! {newline} end, lists:seq(1,100)), lists:foreach(fun(P) -> P ! {done} end, [Printer|Processes]). fizzbuzz_fizzer(Printer) -> receive {n,N} -> if N rem 3 == 0 -> Printer ! {s,"Fizz"}; true -> noop end, fizzbuzz_fizzer(Printer); {done} -> noop end. fizzbuzz_buzzer(Printer) -> receive {n,N} -> if N rem 5 == 0 -> Printer ! {s,"Buzz"}; true -> noop end, fizzbuzz_buzzer(Printer); {done} -> noop end. fizzbuzz_number(Printer) -> receive {n,N} when N rem 5 =/= 0, N rem 3 =/= 0 -> Printer ! {n,N}, fizzbuzz_number(Printer); {n,_N} -> fizzbuzz_number(Printer); {done} -> noop end. fizzbuzz_printer() -> receive {s,S} -> io:format("~s",[S]), fizzbuzz_printer(); {n,N} -> io:format("~w",[N]), fizzbuzz_printer(); {newline} -> io:format("~n",[]), fizzbuzz_printer(); {done} -> noop end.
The output of this last program is consistently interleaved. Moral of the story: just because you send a message to each of a set of processes in a particular order, it does not mean that each message will be received or more importantly acted upon in that particular order. Messages to a process are queued in order, but if you send a message
M1
to process P1
and another message M2
to process P2
, there is no guarantee that process P1
will receive the message before process P2
. There is a guarantee that if you send message M1
to process P1
and another message M2
to the same process P1
, M1
will be received before M2
. But this is a crucial difference.Lastly, an implementation that works, consisting of far too many processes:
-module(fizzbuzz_with_far_too_many_processes). -export([start/0]). -export([fizzbuzz/2]). start() -> % spawn 100 processes to compute the 100 terms lists:map(fun(N) -> spawn(fizzbuzz_with_far_too_many_processes,fizzbuzz,[N,self()]) end, lists:seq(1,100)), lists:foreach(fun(T) -> io:format("~s~n",[element(2,T)]) end, [I || I <- receive_all(100)]). receive_all(M) -> receive_all(M,[]). receive_all(M,L) when M == length(L) -> lists:sort(L); receive_all(M,L) -> receive {N,S} -> receive_all(M,[{N,S}|L]) end. fizzbuzz(N,P) when N rem 3 == 0, N rem 5 == 0 -> P ! {N,"FizzBuzz"}; fizzbuzz(N,P) when N rem 3 == 0 -> P ! {N,"Fizz"}; fizzbuzz(N,P) when N rem 5 == 0 -> P ! {N,"Buzz"}; fizzbuzz(N,P) -> P ! {N,integer_to_list(N)}.
A key thing to learn from this last listing is that erlang tuples are indexed starting at 1. But that is a gripe for another blog entry.
Notes:
[1] add
-smp
to the execution of the program to enable Erlang's use of multiple processors.
No comments:
Post a Comment