DEV Community

Cover image for Can you solve FizzBuzz ... in a tweet?
Oinak
Oinak

Posted on

Can you solve FizzBuzz ... in a tweet?

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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] } * ','
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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] }
Enter fullscreen mode Exit fullscreen mode

which can be translated to the verbose version:

fizz = lambda do |executable|
  test.call(3, "Fizz", executable)
end
Enter fullscreen mode Exit fullscreen mode

What fizzdoes 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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)