2012-11-17

For reasons passing comprehension, my corner of Twitter was touched briefly yesterday by a discussion of the interview process and FizzBuzz.

Whether using FizzBuzz is a useful tool or an admission of defeat I leave to others, but I thought it’d be fun to play with the problem in Erlang.

Problem description

Here’s the example problem:

Write a program that prints the numbers from 1 to 100. But for multiples of three print Fizz” instead of the number and for the multiples of five print Buzz”. For numbers which are multiples of both three and five print FizzBuzz”.

Pretty boring, and hopefully anyone reading this could solve this in their sleep, but even simple problems take on a different flavor when tackled using Erlang and pattern matching.

I wanted to extend it a bit so that instead of hard-coding 3, 5, Fizz” and Buzz” as values, they could be passed as arguments.

Pass, the first

fizzbuzz() ->
    fizzbuzz(1, 100, { 3, "Fizz" }, { 5, "Buzz" }).

I’ve now turned everything into a parameter.

Base case:

fizzbuzz(N, End, _, _) when N > End ->
    ok;

Simple enough. Let’s tackle the FizzBuzz” case next, where N is a multiple of both 3 and 5.

fizzbuzz(N, End, { X, XMsg }, { Y, YMsg }) when N rem X =:= 0, N rem Y =:= 0 ->
    io:format("~s~s~n", [ XMsg, YMsg ]),
    fizzbuzz(N + 1, End, { X, XMsg }, { Y, YMsg });

Cases where only one of the two values is a factor:

fizzbuzz(N, End, { X, XMsg }, { Y, YMsg }) when N rem X =:= 0 ->
    io:format("~s~n", [ XMsg ]),
    fizzbuzz(N + 1, End, { X, XMsg }, { Y, YMsg });

fizzbuzz(N, End, { X, XMsg }, { Y, YMsg }) when N rem Y =:= 0 ->
    io:format("~s~n", [ YMsg ]),
    fizzbuzz(N + 1, End, { X, XMsg }, { Y, YMsg });

And now the simplest case, where we display the number itself because neither 3 nor 5 is a factor:

fizzbuzz(N, End, { X, XMsg }, { Y, YMsg }) ->
    io:format("~B~n", [ N ]),
    fizzbuzz(N + 1, End, { X, XMsg }, { Y, YMsg }).

Test it out:

36> fizzbuzz:fizzbuzz().
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
...

37> fizzbuzz:fizzbuzz(1, 10, { 2, "even" }, { 5, "five" }).
1
even
3
even
five
even
7
even
9
evenfive

Pass, the second

So let’s make it a little more interesting. Even though I parameterized the fizz and the buzz, I’m still stuck with exactly two replacement values.

fizzbuzz2() ->
    fizzbuzz2(1, 100, [ { 3, "Fizz" }, { 5, "Buzz" } ]).

fizzbuzz2(Start, End, Matches) ->
    fizzbuzz2(Start, End, Matches, Matches, []).

Note that the arguments are now a list, and we pass two copies of it into the real” function definitions so that after we work our way down the list of parameters when evaluating one value of N, we’ll have another copy of the original list for the next N value.

This time we don’t have the same pattern matching flexibility since we can’t use arbitrary nested list elements in guard operations.

Base case:

fizzbuzz2(N, End, _Matches, _OrigMatches, _Strings) when N > End ->
    ok;

When we’ve reached the end of all potential matches and none are factors, our parameter list matches this:

fizzbuzz2(N, End, [], OrigMatches, []) ->
    io:format("~B~n", [ N ]),
    fizzbuzz2(N + 1, End, OrigMatches, OrigMatches, []);

When we’ve reached the end of all potential matches and we have found at least one factor, our last argument will no longer be an empty list but will be a list containing at least one nested string:

fizzbuzz2(N, End, [], OrigMatches, Strings) ->
    io:format("~s~n", [ lists:reverse(Strings) ]),
    fizzbuzz2(N + 1, End, OrigMatches, OrigMatches, []);

(As usual, when accumulating a list of values in a tail recursive manner, the list will be LIFO, so we need to reverse it to turn it back into a proper order.)

And finally, the two cases where the head of the match list either is, or is not, a factor of N:

fizzbuzz2(N, End, [ { X, Xmsg } | Tail ], OrigMatches, Strings) when N rem X =:= 0 ->
    fizzbuzz2(N, End, Tail, OrigMatches, [ Xmsg | Strings]);

fizzbuzz2(N, End, [H | Tail], OrigMatches, Strings) ->
    fizzbuzz2(N, End, Tail, OrigMatches, Strings).

Run it:

40> fizzbuzz:fizzbuzz2(2, 13, [{ 2, "two" }, { 3, "three" }, { 4, "four" }]).
two
three
twofour
5
twothree
7
twofour
three
two
11
twothreefour
13
ok
erlang
Previous post
Game of Twenty-One MIT 6.001 in action, with edoc and type specs
Next post
Tech Mesh 2012 Erlang fans, unite