FizzBuzz is a (in)famous programming puzzle that it is used mainly just to filter people who can code from people who can't.
The problem statement is as follows:
Write a function that prints the 100 first integers, but for every multiple of 3 prints "Fizz" (instead or the number), for every multiple of 5 prints "Buzz", for a mutipe of both 3 and 5 prints "FizzBuzz",
One naive impementation would be something like:
def fizz_buzz
(1..100).each do |n|
print 'Fizz' if n % 3 == 0
print 'Buzz' if n % 5 == 0
print n unless n % 3 == 0 || n % 5 == 0
print "\n"
end
end
This works, but has several shortcomings with no easy workarounds. The most immediate one: you are checking modulo twice (or have cumbersome elses)
See this alternative:
def fizz_buzz
(1..100).each do |n|
if n % 3 == 0
puts "Fizz#{n % 5 == 0 ? 'Buzz' : ''}"
else
puts "#{n % 5 == 0 ? 'Buzz' : n}"
end
end
end
Where you only repeat the 5 check, but the code is extra complex.
This is a bad test, not a good predictor of future software engineering performace. But that does not mean you cannot use it to learn stuff about ruby. For example, you could choose to solve it with no if's, no methods, no classes and no method calls (other than operator-like)...
Let me take you to the end and then see how do we get there:
-> n {
t = ->(d, s, x) { n % d == 0 && -> _ { s + x[''] } || x }
f = -> x { t[3, "Fizz", x] }
b = -> x { t[5, "Buzz", x] }
f[b[-> x { x }]][n]
}.yield_self { |fb|
p (1..100).map { |n| fb[n] } * ','
}
What do we get here:
- "Fizz" appears only once
- "Buzz" appears only once
- 3 appears only once
- 5 appears only once
- modulo check is generalized and appears only once
- we use lambdas
- we use function composition
- we use higher order functions (functions receiving functions as parameter)
And now the parts:
Test function
t = ->(d, s, x) {
n % d == 0 && -> _ { s + x[''] } || x
}
But for the analysis lets have better identifiers:
test = lambda do |divisor, string, executable|
# if the number is a multiple of divisor...
number % divisor == 0 &&
# return a lambda that prefixes string
(lambda do |_| string + executable[''] end) ||
# otherwise return the received function
executable
end
I don't know how much ruby you know so I will try to explain it all:
lambda
creates an anonymous method that can be stored in a variable or passed as argument
a = lambda do |x|
puts "#{x} #{x}"
end
a.call('hi')
prints hi hi
->(x){ puts "#{x} #{x}"}
is just an alternate syntax to define lambdas, it is sometimes called the stabby operator
Inside the test lambda we have this structure: a && b || c
&&
is ruby and operator (for expressions, there is also and
for action, with different precedence)
and ||
is the or.
This is used in substitution of if
and else
a && b
short-cirtuits in ruby so it only evaluates b
if a
is true (if a
is false, the and already is).
On the other hand: b || c
only evaluates c
if b
is falsey (if it's true, the expression already is)
So the translation of the test function is:
test = lambda do |divisor, string, executable|
if number % divisor == 0
(lambda { |_| string + executable[''] })
else
executable
end
end
This whole return a function business sounds complicated but is key to be able to use test for both Fizz and Buzz
Fizz function
Test function is pretty generic, but can be translated as:
if n is a mutiple of d, perfix s to what you receive, otherwise leave it be.
Being so generic, allows us to build two functions, f
(for fizz) and b
(buzz) by feeding different arguments to test
, so
f = -> x { t[3, "Fizz", x] }
which can be translated to the verbose version:
fizz = lambda do |executable|
test.call(3, "Fizz", executable)
end
What fizz
does is call test
(t
) with two literal parameters, 3
and "Fizz"
and a dynamic one, executable
(x
).
With this example you can imagine that buzz
(b
) is constructed in very much the same way replacing 3 with 5 and "Fizz" with "Buzz".
Function composition
One of the things we did not like on the original version was the repeated checks. The only way to avoid them in this case is to nest them.
We are building a fizz function that receives a buzz function as an argument so that it can run that code sometimes.
There is a whole branch os programming/mathematics that deals with this kind of thing. It is called Lambda Caculus and that is where the name of ruby's lambda
function comes from.
Multiple function composition
Now we have the last mysterious part:
f[
b[
-> x { x }
]
][n]
f: returns a function that
if n is divisible by 3
returns a function that returns:
"Fizz" + received code run on "" (empty string)
else
returns a function that runs the received code
The code that f receives is b[->x{x}]
b: returns a function that
if n is divisible by 5
returns a function that returns:
"Buzz" + received code run on "" (empty string)
else
returns a function that runs the received code
But now the most important:
b
receives this code -> x { x }
This is a function that returns its argument. Think of:
def executable(x)
return x
end
This is the part of the code that will return the integer ONLY when it is not a multiple of neither 3 nor 5 because, and this is the key, both f
and b
run this function on their else
sides, and run something like executable("")
(or x['']
in the abbreviated version) instead when the modulo is 0.
I am almost sure that this might not be absolutely clear, so please, copy this code, paste it into your irb
, play with it... and come with your questions.
I would love to improve this article with each question received in the comments, and encourage you to play with even the smallest piece of code you lay your hands on, to push the boundaries of your knowledge, or at east have some fun.
Top comments (0)