Task: Implement a function which loops from 1 to 100 and prints "Fizz" if the counter is divisible by 3. Prints "Buzz" if the counter is divisible by 5 and "FizzBuzz", when the counter is divisible by 3 and 5.
The instructor gave me the hint, that it's solvable using the ternary operator. Otherwise I would have used an if-then-else approach.
My solution:
def fizz_buzz
for i in 1...100
puts i % 3 == 0 ? i % 5 == 0 ? "FizzBuzz" : "Fizz" : i % 5 == 0 ? "Buzz" : i
end
end
What's your opinion about the approach? Would you still consider it as readable?
I personally consider the readability hard on the border.
Which approach would you prefer and why?
4 Answers 4
Ternary statement is cleaner and often faster to comprehend than if/else; especially true for languages with ceremony syntax, such as curly braces. And I like nested ternary statements (or is it singular?) but it's important to format to help the reader with logic flow. Assume the reader knows the operator structure and resist over-indentation resulting eye boggling pseudo if/else.
def fizz_buzz
for i in 1...100
puts i % 3 == 0 ? i % 5 == 0 ? "FizzBuzz" : "Fizz" :
i % 5 == 0 ? "Buzz" : i
end
end
-
\$\begingroup\$ (I'd use parentheses rather than multiple spaces to indicate association.) \$\endgroup\$greybeard– greybeard2023年06月26日 06:44:28 +00:00Commented Jun 26, 2023 at 6:44
-
\$\begingroup\$ hmmm... I hesitate. I've not used parenthesis except as necessary for operator precedence. Here it serves to signal AND and/or OR and that also feels like "wink, wink, I'm an if/ else." I like your suggestion of inline if's better. \$\endgroup\$radarbob– radarbob2023年06月26日 07:10:27 +00:00Commented Jun 26, 2023 at 7:10
-
\$\begingroup\$ Space update: I think the spacing (see above) does not help. It is too subtle unless the spacing is exaggerated. In any case it doesn't feel like it's adding to understanding. \$\endgroup\$radarbob– radarbob2023年06月26日 20:20:45 +00:00Commented Jun 26, 2023 at 20:20
No nested if's (and no ternary operators):
def fizzbuzz
(1..100).each do |n|
res = ""
res << "Fizz" if n % 5 == 0
res << "Buzz" if n % 3 == 0
res << n.to_s if res.empty?
puts res
end
end
With one extra line of code this transforms to a FizzBuzzBazz.
-
\$\begingroup\$ The observation here seems to be "Don't use ternary conditional", but it's not clear why you say that. And I don't get the bit about nested
if
, since the code under review has noif
s at all. \$\endgroup\$Toby Speight– Toby Speight2023年09月18日 09:31:38 +00:00Commented Sep 18, 2023 at 9:31 -
\$\begingroup\$ This example shows that
if
s without nested branching can increase clarity even compared to ternary statements. Further, the Ruby-esque "trailing IF" not only complements clarity but emphasizes the result-value over the condition that emits that value. One could say it coveys a precedence or preference of returnable values. Again it is about what you want to communicate and emphasize about the code. \$\endgroup\$radarbob– radarbob2024年01月21日 23:17:09 +00:00Commented Jan 21, 2024 at 23:17
In all the cases except the one where performance is a top priority, I'd prefer the most readable approach:
def fizz_buzz
100.times do |i|
if i % 3 == 0 && i % 5 == 0
puts "FizzBuzz"
elsif i % 3 == 0
puts "Fizz"
elsif i % 5 == 0
puts "Buzz"
else
puts i
end
end
end
This code immediately tells you that there are 4 cases, you may easily see the conditions for each of them, all the outcomes are clear and the code is still very simple. Other programmers (or future you) will spend almost no time to read and understand it.
Some people will say that this code is not optimal because it does more checks than the required minimum, but that's usually much less important than readability. It'll certainly take several additional nanoseconds, but in most cases it is negligible. For example, in web development (where Ruby is mostly used), typical response times are in tens or hundreds of milliseconds. Developer time, however, costs a lot. Caring about code readability pays
Also, notice that i % 3 == 0 && i % 5 == 0
is the same as i % 15 == 0
, but I'd suggest not to make this replacement either, because i % 3 == 0 && i % 5 == 0
is more intent-revealing
I suggest reading "99 bottles of OOP" by Sandi Metz for a deeper perspective on that.
-
1\$\begingroup\$ "... but I'd suggest not to make this replacement either, because
i % 3 == 0 && i % 5 == 0
is more intent-revealing" One could also add a comment to explain if one wanted to opt for fewer operations. \$\endgroup\$2023年12月11日 21:53:37 +00:00Commented Dec 11, 2023 at 21:53 -
1\$\begingroup\$ Right, something like
i % 15 == 0 # both 3 & 5
will be even more readable. \$\endgroup\$Vizvamitra– Vizvamitra2023年12月11日 22:07:44 +00:00Commented Dec 11, 2023 at 22:07
A post just to suggest a different formatting of radarbob's suggestion
def fizz_buzz
for i in 1...100
puts i % 3 == 0 ? i % 5 == 0 ? "FizzBuzz" : "Fizz"
: i % 5 == 0 ? "Buzz" : i
end
end
-
\$\begingroup\$ I never coded ruby in earnest. \$\endgroup\$greybeard– greybeard2023年06月27日 18:56:20 +00:00Commented Jun 27, 2023 at 18:56
-
\$\begingroup\$ The more one learns of Ruby the more one enjoys it and indeed love it in comparison to others. "Everything is an object" is freeing; syntax is refreshingly clean. \$\endgroup\$radarbob– radarbob2023年06月27日 23:30:25 +00:00Commented Jun 27, 2023 at 23:30
-
\$\begingroup\$ I'd suggest your format highlights " i % 3"s TRUE : FALSE clauses and my answer emphasizes "I % 3" OR "i % 5". Both good. It's about what you want to say to the reader. \$\endgroup\$radarbob– radarbob2023年06月27日 23:37:51 +00:00Commented Jun 27, 2023 at 23:37
if
: How would you doif-else
? \$\endgroup\$