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”.
Don't look until you want to see how I thought through it:
puts (1..100).collect { |i| (fb = [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join).empty? ? i : fb }
or
puts (1..100).map { |i| (fb = [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join).empty? ? i : fb }
Use map
if you want to save 4 characters :)
We need to take a number and see if it is divisible by 3, 5 respectively.
In programming, we have a % operator called Modulo, it will divide a number and return the remainder.
e.g.
3 % 3
=> 0
4 % 3
=> 1
5 % 3
=> 2
6 % 3
=> 0
Therefore, the most basic adaptation to check if a number is divisible would be something like:
# Loops through each number, 1-100
(1..100).each do |i|
if i % 3 == 0 and i % 5 == 0
# Checks if integer is divisible by both 3 & 5
puts "FizzBuzz"
elsif i % 3 == 0
# Checks if integer is divisible by only 3
puts "Fizz"
elsif i % 5 == 0
# Checks if integer is divisible by only 5
puts "Buzz"
else
# Otherwise prints the number itself
puts i
end
end
When run, we get a result like:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz
=> 1..100
That seems to be the simplest working version, but there is a lot of similar logic, multiple calls to puts
, and the return value is 1..100
. We can refactor.
As mentioned there is a repeating pattern in our logic in what determines the appropriate output. When Using the modulo operator, we are looking for clean division, meaning no remainder, meaning that value must always equal 0 to be true.
Restated: The number qualifies for a 'special output' when the result == 0
0 is a special number in programming that can be used in unique ways. One way is, we could use 0 to return the first element in an array, such as:
["Fizz"][0]
=> ["Fizz"]
Which stands to reason we could do something like:
["Fizz"][i % 3]
and
["Buzz"][i % 5]
We have a lot of capabilities when it comes to manipulating data in an array. We can nest arrays, join arrays, and so much more. Let's combine our two arrays:
# let's assume i = 3
i = 3
[["Fizz"][i % 3], ["Buzz"][i % 5]]
=> ["Fizz", nil]
# Note, the index 2 of buzz is nil, there is no third element in the array
# let's assume i = 5
i = 5
[["Fizz"][i % 3], ["Buzz"][i % 5]]
=> [ nil, "Buzz"]
# Similarly, as 5 is greater than 3, the remainder 3 returns, the 3 index of ["Buzz"] is also nil
# let's assume i = 15
i = 5
[["Fizz"][i % 3], ["Buzz"][i % 5]]
=> [ "Fizz", "Buzz"]
# now this is interesting, and some data we can work with
# and lastly, let's assume a 'non-qualifier number', i = 2
i = 2
[["Fizz"][i % 3], ["Buzz"][i % 5]]
=> [ nil, nil]
Ok, now we have two values in an array, each with a possibility of being nil, "fizz" or "buzz"
[nil, nil] # Non qualifiers
["Fizz", nil] # Numbers only divisible by 3
[nil, "Buzz"] # Numbers only divisible by 5
["Fizz", "Buzz"] # Numbers divisible by both 3 & 5
So we have these pesky nil numbers we would like to get rid of, as they contribute nothing, and in the case of nil, we want to simply return the number.
The Ruby Array Class gives us a compact
method that will remove any nil values from an array, thus if we run .compact on the end of them, it will clean right up:
[nil, nil].compact
=> []
["Fizz", nil].compact
=> ["Fizz"]
[nil, "Buzz"].compact
=> ["Buzz"]
["Fizz", "Buzz"].compact
=> ["Fizz", "Buzz"]
Now that we've cleaned those up, we can join our strings in the array together using our join method:
[nil, nil].compact.join
=> ""
["Fizz", nil].compact.join
=> "Fizz"
[nil, "Buzz"].compact.join
=> "Buzz"
["Fizz", "Buzz"].compact.join
=> "FizzBuzz"
Ok, These outputs look like something we like. We'll still need to solve for the "" empty string when i is a non-qualifying number, but that's now only one conditional statement.
Let's string all this back together:
[["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join
And now apply this back to our original solution:
(1..100).each do |i|
puts [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join
end
Result:
Fizz
Buzz
Fizz
Fizz
Buzz
Fizz
FizzBuzz
Fizz
Buzz
Fizz
Fizz
Buzz
Fizz
FizzBuzz
Fizz
Buzz
Fizz
Fizz
Buzz
Fizz
FizzBuzz
Fizz
Buzz
Fizz
Fizz
Buzz
Fizz
FizzBuzz
Fizz
Buzz
Fizz
Fizz
Buzz
Fizz
FizzBuzz
Fizz
Buzz
Fizz
Fizz
Buzz
Fizz
FizzBuzz
Fizz
Buzz
Fizz
Fizz
Buzz
=> 1..100
So we see the logic part of it is working, now we just need to get our integers back when it is an empty string:
(1..100).each do |i|
if [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join == ""
puts i
else
puts [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join
end
end
Even this can be refactored a few steps further. == "" is not a great conditional statement, we have multiple ruby methods that check empty strings for us: empty?
, blank?
, present?
(but the opposite). I like empty because not only will it catch "", but also nil & empty arrays.
(1..100).each do |i|
if [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join.empty?
puts i
else
puts [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join
end
end
We still have that big long [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join
that is in there twice. Seems repetitive. Frankly, this is a simple if else statement, and a ternary operator could shrink the logic part of it down to a cleaner, simpler, one line solution for us:
(1..100).each do |i|
puts (fb = [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join).empty? ? i : fb
end
Note, I set a variable fb here, so I can reference that long equation in my else clause, without retyping
Result:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz
=> 1..100
Ok it's looking pretty good. I'm satisfied with the printing, but I still don't like how it's returning the 1..100 range as a result. Instead of each, let's use map or collect (synonyms). Remember, collect/map will pull it all into a single array/output.
(1..100).collect do |i|
(fb = [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join).empty? ? i : fb
end
# Note I'm not calling puts in this example...yet.
The nice thing about the map/collect method is there is a shorthand way of writing blocks as well, rather than .collect do |i| ... end
, we can use .collect { |i| .... }
syntax.
(1..100).collect { |i| (fb = [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join).empty? ? i : fb }
And re-adding puts
to the beginning of the line accomplishes our print instruction
puts (1..100).collect { |i| (fb = [["Fizz"][i % 3], ["Buzz"][i % 5]].compact.join).empty? ? i : fb }
Outputs:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz
=> nil
And returns nil, just like we want.
I got mine down to 85 characters: