-3

I am trying to complete a coding exercise in Ruby, which is as follows:

TODO: starting with an array of integers, return an array with integers and 'Fizz', 'Buzz' or 'FizzBuzz'

Write a method fizz_buzz, which takes a number as an argument, and return an array of number elements from 1 to number, but replaces some of them according to these rules:

  • If the number is divisible by 3, then replace it by 'Fizz'
  • If the number is divisible by 5, then replace it by 'Buzz'
  • If the number is divisible by both 3 and 5, then replace it by 'FizzBuzz'Write

If I follow a rubocop style guide, which we have been asked to use after completed the task, and, as instructed, use .zero instead of == 0? my method fails and I don't get why.

My now failing solution with the edits required by my style guide:

def fizz_buzz(number)
  fail ArgumentError, "#{number} is less than 1" if number < 1
  a = [number]
  while number > 1
    number = number - 1
    a.unshift(number)
    a.map! { |x| (x % 15).zero? ? 'FizzBuzz' : x }
    a.map! { |x| (x % 3).zero? ? 'Fizz' : x }
    a.map! { |x| (x % 5).zero? ? 'Buzz' : x }
  end
  a
end

  should return the array [ 1, 2, 'Fizz' ] for number 3 (FAILED - 1)
  should return the array [ 1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7 ] for number 7 (FAILED - 2)
  should return an array with 'FizzBuzz' at the 15th element of the array (15 is divisible by both 3 and 5) (FAILED - 3)

Failures:

  1) fizz_buzz should return the array [ 1, 2, 'Fizz' ] for number 3
     Failure/Error: a.map! { |x| (x % 5).zero? ? 'Buzz' : x }

 NoMethodError:
   undefined method `zero?' for "Fizz":String
 # ./lib/fizz_buzz.rb:12:in `block in fizz_buzz'
 # ./lib/fizz_buzz.rb:12:in `map!'
 # ./lib/fizz_buzz.rb:12:in `fizz_buzz'
 # ./spec/fizz_buzz_spec.rb:13:in `block (2 levels) in <top (required)>'

  2) fizz_buzz should return the array [ 1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7 ] for number 7
     Failure/Error: a.map! { |x| (x % 5).zero? ? 'Buzz' : x }

 NoMethodError:
   undefined method `zero?' for "Fizz":String
 # ./lib/fizz_buzz.rb:12:in `block in fizz_buzz'
 # ./lib/fizz_buzz.rb:12:in `map!'
 # ./lib/fizz_buzz.rb:12:in `fizz_buzz'
 # ./spec/fizz_buzz_spec.rb:17:in `block (2 levels) in <top (required)>'

  3) fizz_buzz should return an array with 'FizzBuzz' at the 15th element of the array (15 is divisible by both 3 and 5)
     Failure/Error: a.map! { |x| (x % 5).zero? ? 'Buzz' : x }

 NoMethodError:
   undefined method `zero?' for "Fizz":String
 # ./lib/fizz_buzz.rb:12:in `block in fizz_buzz'
 # ./lib/fizz_buzz.rb:12:in `map!'
 # ./lib/fizz_buzz.rb:12:in `fizz_buzz'
 # ./spec/fizz_buzz_spec.rb:21:in `block (2 levels) in <top (required)>'

My working solution which fails the style guide:

def fizz_buzz(number)
  fail ArgumentError, "#{number} is less than 1" if number < 1
  a = [number]
  while number > 1
    number = number - 1
    a.unshift(number)
    a.map! { |x| (x % 15) == 0 ? 'FizzBuzz' : x }
    a.map! { |x| (x % 3) == 0 ? 'Fizz' : x }
    a.map! { |x| (x % 5) == 0 ? 'Buzz' : x }
  end
  a
end

Solution supplied with the task assigned:

# def fizz_buzz(number)
#   fail ArgumentError, "#{number} should be greater than 1" if number < 1
#   (1..number).map do |i|
#     if (i % 3).zero? && (i % 5).zero?
#       'FizzBuzz'
#     elsif (i % 3).zero?
#       'Fizz'
#     elsif (i % 5).zero?
#       'Buzz'
#     else
#       i
#     end
#   end
# end
Grr La
  • 46
  • 6
  • 1
    What is "an array of integers, 'Fizz', 'Buzz' or 'FizzBuzz'"? It does not make sense. – sawa Jun 12 '18 at 10:40
  • Both of them work and produce the correct FizzBuzz problem output. What exactly fails? The question is confusingly confusing. What version is wrong, why is it wrong, is there an error, what is Rubocop's complaint, what did you do about it, and what seems to be the problem? "My solution with style fails but if I use ".zero?" it fails" fails to communicate the issue. – Amadan Jun 12 '18 at 10:40
  • @Amadan Criticism accepted and noted. I hope it is more readable now. It was my first post. – Grr La Jun 12 '18 at 11:34
  • @sawa Elaborated on the TODO of the exercise. I hope it adds clarity. Thanks. – Grr La Jun 12 '18 at 11:35

1 Answers1

1

Now that we see the failing code, here's why it fails:

You have an array a which I suppose you intended to contain the results. You prepend the numbers to it, and fix the numbers that need to say "Fizz" or "Buzz" or "FizzBuzz". This would work, but for one critical failure: you process the array in every iteration of the loop. If you just fixed it once, just before you return it, it would have been fine. However, this way, what happens is (e.g. for input value 4):

  • You start your array with 4
  • You run the fixes, but 4 is good, does not need fixing
  • You prepend 3
  • You run the fixes; the array is now ["Fizz", 4]. So far so good.
  • You prepend 2; the array is [2, "Fizz", 4]
  • You run the fixes. 2 passes with no intervention, then it's "Fizz"'s turn.
  • "Fizz" is not a number, so you don't invoke Number#%, the remainder operator, but String#%, the formatting operator. There's no format sigils in the string "Fizz", so the formatting operator does nothing interesting and returns the string "Fizz".
  • Then you test if it's zero. When you tried "Fizz" == 0, it would have been "Duh, no" and just returned false, since all classes (starting with BasicObject) define #==. But "Fizz".zero? fails, because unlike Number#zero?, String#zero? is not a thing.
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I get it now. Thanks for your clarity and speed. Answer accepted and upvoted. You are no fool ;) "Thanks for the feedback! Votes cast by those with less than 15 reputation are recorded, but do not change the publicly displayed post score." – Grr La Jun 12 '18 at 11:50
  • Fools can only learn; when you think you know something, you stop learning. It's a good reminder. Good on you, most people just think I misspelled Amanda. :P And don't worry about my score, I'll live :) – Amadan Jun 12 '18 at 11:54
  • Just remembered, I should say, there's much cooler ways to get sequential numbers into an array in Ruby than prepending them in a `while` loop; for example, `(1..number).map {...}` is short and sweet. – Amadan Jun 12 '18 at 14:43
  • Thanks. I had seen that in the given solution when I was refactoring (see the last paragraph in the question where it is given). There is nothing further to refactor than that is there? – Grr La Jun 12 '18 at 14:46
  • Err, I'm blind tonight. Of course, it's right there. The only other thing I can think of is that `(i % 3).zero? && (i % 5).zero?` is equivalent to `(x % 15).zero?`, but you have that as well. The former is a tad slower and a tad clearer. So, nope, that's pretty much perfect. – Amadan Jun 12 '18 at 14:49