0

I am trying to return an array containing the numbers from 1 to N, where N will never be less than 1, with the following conditions:

  • If the value is a multiple of 3: use the value 'Fizz' instead.
  • If the value is a multiple of 5: use the value 'Buzz' instead.
  • If the value is a multiple of 3 and 5: use the value 'FizzBuzz' instead.

This is what I have right now. Am I going in the right direction?

def fizzbuzz(n)
  x = [1..n]
  x.map { |i|
    if (i % 3 == 0 && i % 5 == 0)
      'FizzBuzz'
    elsif (i % 3 == 0 && i % 5 != 0) 
      'Fizz'
    elsif (i % 5 == 0 && i % 3 != 0)
      'Buzz'
    end

  puts x
end
sawa
  • 165,429
  • 45
  • 277
  • 381
user3408293
  • 1,377
  • 6
  • 18
  • 26

4 Answers4

4

In such a short piece of code, you have so many crucial mistakes and bad habits.

  1. [Bug] Your { is not closed.
  2. [Bug] You have the wrong object [1..n].
  3. [Bug] You are trying to create an array using map, but somehow you are not doing anything with it, and are instead doing puts on the original object. That is meaningless.
  4. [Bug] You haven't given an appropriate value when neither of the condition is met.
  5. [Refactor] Your condition is redundant. You only need to either put the (i % 3 == 0 && i % 5 == 0) condition at the beginning, or use the i % 5 != 0 and i % 3 != 0 conditions, but not both. The former is smarter.
  6. [Refactor] You are using x only once, where chaining is easy. You should do chaining.
  7. [Refactor] There is zero? method that you can use.
  8. [Refactor] You had some unnecessary parentheses.
  9. [Style] Since the conditioned value is short enough, you can put them each in one line.
  10. [Style] It is pretty much established among Rubyists to use do ... end rather than { ... } when the block exceeds a single line.

Four bugs in thirteen lines is pretty much. A corrected code would be:

def fizzbuzz(n)
  (1..n).map do |i|
    if (i % 3).zero? && (i % 5).zero? then 'FizzBuzz'
    elsif (i % 3).zero? then               'Fizz'
    elsif (i % 5).zero? then               'Buzz'
    else                                   i
    end
  end
end
puts fizzbuzz(10)
sawa
  • 165,429
  • 45
  • 277
  • 381
  • Thanks! I managed to fix all the errors you mentioned, except I was unable to figure out why x = [1..n] does not work and x = 1..n does – user3408293 Apr 26 '14 at 12:01
  • Method `each` iterates over each element if the receiver is an array. If you iterate over `[1..n]`, what would be its elements? The first (and the only) element is `1..n`. Is that what you want as `i`? If you iterate over a range, each entry would be an element within that range; that is what you want as `i`. – sawa Apr 26 '14 at 12:02
  • So when inside [] the 1..n does not represent a range but a singular value such "1..n" ? Is that what you're getting at? – user3408293 Apr 26 '14 at 12:04
  • 1
    @user3408293: `1..n` represents a range object. `[1..n]` represents an array with one element — that `1..n` range object. – Denis de Bernardy Apr 26 '14 at 12:07
  • I get it, thanks! I was thinking about it in a weird way, still getting used to thinking like a programmer. Thanks! – user3408293 Apr 26 '14 at 12:10
1

You're in the right direction. The biggest issue is the array definition. The following:

x = [1..n]

… will create an array with a single value — a 1..n range object.

Any of these are valid alternatives:

x = 1..n          # no need for an array since you're using map
x = (1..n).to_a   # converts the range to an array explicitly
x = [*1..n]       # unsplats the range

The other big issues, as point out by @sawa, are an unclosed bracket and the use of map instead of map! (alternatively, use x = x.map { … } or simply return x.map { … } and move puts outside of the function).

Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
  • Yup, this was stumping me for sure! Thanks! I think I need to read more about map function since I didn't know that map can't work with x = [1..n] I'm still not sure I understand why it won't work but I'll keep looking. This well definitely help. – user3408293 Apr 26 '14 at 12:03
  • Oh, it actually works fine with `[1..n]`. Try this in irb: `[1..2, 2..4, 3..6].map { |i| p i }`. Your confusion comes from thinking Ruby will unsplat the range implicitly. It won't — you'd want one of the three alternatives I highlighted. – Denis de Bernardy Apr 26 '14 at 12:04
  • hmmmmm damn okay I'm confused, I'm not understanding why x = [1..n] won't work since what I need is an array. From the poster below I think maybe it cuz when in [] the 1..n is interpreted at "1..n" rather than a range? – user3408293 Apr 26 '14 at 12:07
  • See the last comment I posted on sawa's answer: a range is an object like just about any other thing in Ruby, and you can have an array of ranges just like you can have an array of integers or strings. – Denis de Bernardy Apr 26 '14 at 12:08
  • I need to go learn what unsplat is never even heard of that before. I'm a super newb. – user3408293 Apr 26 '14 at 12:08
  • I think I get it now, just clicked. – user3408293 Apr 26 '14 at 12:09
  • @user3408293 - I believe you better avoid [*1..n] as it actually creates the array [1,2,3,...,n], as opposed to using (1..n) which simply provides the numbers when needed - much more memory efficient. – Uri Agassi Apr 26 '14 at 14:09
0

Some code optimization, it is just additionals to sawa's answer:

def fizzbuzz(n)
   x = Array.new(n) { '' }.map.with_index(1) do |v,i|
      v << 'Fizz' if (i % 3).zero?
      v << 'Buzz' if (i % 5).zero?
      v.empty? && i || v
   end
end
fizzbuzz(15)
# => [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"] 

Comments:

  1. Creating a new array filled with empty strings with nth dimension.
  2. Since indexation begins from 1, incrementing to 1 in a loop.
  3. Using three condition we set a proper new value for each one in the array.
  4. The line v.empty? && i || v can be replace with v.empty? ? i : v, but it is for a taste. I prefer && || pair.
Малъ Скрылевъ
  • 16,187
  • 5
  • 56
  • 69
  • 1
    **1.** You can do `with_index(1)` and get rid of `i += 1`. **2.** `v.empty? && i || v` is hard to read. `v.empty? ? i : v` is reader friendly. – sawa Apr 26 '14 at 12:35
-1
def fizzbuzz(n)
  [*1..n].each { |i|
      if i % 3 == 0 && i % 5 == 0
         'FizzBuzz'
      elsif  i % 3 == 0 
         'Fizz'
      elsif i % 5 == 0
         'Buzz'
      end
   }
end

You can use the case statements as well:

[*1..16].each { |i|
  case 
  when (i % 3).zero? && (i % 5).zero?
      ‘FizzBuzz'
  when  (i % 3).zero?
      'Fizz'
  when (i % 5).zero?
      'Buzz'
  end
}
Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • Why did they downvote you? BTW what is a case statement? – user3408293 Apr 26 '14 at 12:11
  • yes, I am not sure, why down-voted, really like to know. Case statement is a selection statement that lets you transfer control to different statements, just like if-else, but helpful if you have too many conditions, some example of it in ruby are [here](http://stackoverflow.com/questions/948135/how-can-i-write-a-switch-statement-in-ruby) – Saurabh Apr 26 '14 at 12:16
  • Your original code had `n.times`, which gives indices starting from `0`, not `1`. – sawa Apr 26 '14 at 12:19
  • ohh, yes, I realised that and modified that instantaneously. – Saurabh Apr 26 '14 at 12:21
  • It didn't look instantaneously to me. At least it was still there for a few minutes after someone downvoted it. In fact, there is seven minutes between the post and the first edit. – sawa Apr 26 '14 at 12:26