String can't be coerced into Fixnum?

3.1k views Asked by At

I am attempting a "99 Bottles" program. I tried to simplify it, but I got "string cant be coerced into a Fixnum":

num_at_start = 99
num_now = num_at_start         
bobo = " bottles of beer on the wall"
bob = " bottles of beer!"
while num_now > 2
  puts num_now.to_s + bobo.to_s    
  puts num_now.to_s + bob.to_s
  puts num_at_start.to_i - 1 + bobo.to_s
  gets
end
2

There are 2 answers

0
Aleksei Matiushkin On

The problem is here:

puts num_at_start.to_i - 1 + bobo.to_s

Ruby suggests types of resulting expressions as args come to interpreter, from left to right. Here you attempt to sum two integers, making the result to be an integer. Fixnum#+ requires instance of Fixnum as an operand, but there bobo.to_s, which is String, comes.

You should use inplace eval here:

puts "#{num_at_start - 1}#{bobo}"

The whole while loop should be actually written as:

while num_now > 2
  puts "#{num_now}#{bobo}"

  puts "#{num_now}#{bob}"
  puts "#{num_at_start - 1}#{bobo}"
  gets
end

BTW, there is another problem: an endless loop; but it’s up to you to fix this error after you got the code you have now to work.

0
the Tin Man On

Here's how I'd write the code:

BOBO = '%d bottles of beer on the wall'
BOB = '%d bottles of beer!'

num_at_start = 2
while num_at_start > 0
  bobo_str ||= BOBO % num_at_start
  puts bobo_str
  puts BOB % num_at_start
  puts 'Take one down and pass it around'
  num_at_start -= 1

  bobo_str = BOBO % num_at_start
  puts bobo_str
  puts
end

Which outputs:

# >> 2 bottles of beer on the wall
# >> 2 bottles of beer!
# >> Take one down and pass it around
# >> 1 bottles of beer on the wall
# >> 
# >> 1 bottles of beer on the wall
# >> 1 bottles of beer!
# >> Take one down and pass it around
# >> 0 bottles of beer on the wall
# >> 

There are some things I did differently:

  • BOBO and BOB are now String formats. See the String#% and Kernel#sprintf documentation for an explanation.
  • There's no point doing num_now = num_at_start. Just work with num_at_start.
  • The loop test needs to trigger while the value is greater than zero, so write the conditional to reflect that. Doing otherwise will confuse you and anyone else working on your code later.
  • bobo_str ||= BOBO % num_at_start is a short-hand way of initializing bobo_str if it hasn't been set. ||= is basically "assign unless it's set".

Instead of using a while loop, I'd recommend using Ruby's downto.

2.downto(1) do |num_at_start|
  bobo_str ||= BOBO % num_at_start
  puts bobo_str
  puts BOB % num_at_start
  puts 'Take one down and pass it around'

  bobo_str = BOBO % (num_at_start - 1)
  puts bobo_str
  puts
end