Ruby if/elseif coding error - ruby

I'm writing a simple and small program in Ruby to output the ordinal of a number based on user input. Here is what I wrote:
puts "Enter a number"
number = gets.chomp.to_i
conversion = number % 10
if number == 11 || number == 12 || number == 13
puts "That is #{number}th"
end
if conversion == 1
puts "That is #{number}st"
elsif conversion == 2
puts "That is #{number}nd"
elsif conversion == 3
puts "That is #{number}rd"
else
puts "That is #{number}th"
end
As my code shows, I wanted to take the "number" from the user input, cut off the last number, and use that single digit number to determine whether "number" should be a (fir)st, (seco)nd, (thi)rd, or (four)th type of ordinal. The "conversion" value should convert that for me. However, it can be seen that the numbers 11, 12, and 13 are exceptions. My program works fine except that when I input one of these three values, I get two outcomes, not one, in the terminal such as:
That is 11st
That is 11th
My intention is that "11th" should be displayed, not "11st". Even though I tried to write an exception into my code, the "conversion" still gets executed. I'm not sure what error I made, but is there a way I can isolate the first if statement, so that my conversion does not include 11, 12, and 13?

That is exactly how you have the program written!
If the number is 11,12,13 it will trigger the first if statement, as you expect it to. It will then go to the next if statement because it doesn't have any reason to end. puts is not a return/break statement (a return/break statement would break this code, but that is a different problem) so code will continue to run until the end.
The easiest solution would be to simply combine the two if statements into your main if/else statement:
if number == 11 || number == 12 || number == 13
puts "That is #{number}th"
elsif conversion == 1
puts "That is #{number}st"
elsif conversion == 2
puts "That is #{number}nd"
elsif conversion == 3
puts "That is #{number}rd"
else
puts "That is #{number}th"
end
This will prevent any number from being able to be true in different if loops.

Related

Simple program but so very stuck- Loops in Ruby

I have to write a program which asks the user to enter a number.
The program keeps on asking the user for a number until the user types 'Stop'
at which point the sum of the numbers that the user has entered should be printed.
I've tried many,many things and none of my ideas work.
This is what I have - but I can that it isn't correct. What am I doing wrong?
I've only used while loops and arrays
total_user_input = []
# As long as the user inputs a number, the program will keep putting Give me a number
# and then adding that number to the total_user_input array.
puts "Give me a number: "
while user_input = gets.chomp.to_i
#add the input to the array total_user_input
total_user_input.push(user_input.to_i)
puts "Give me a number: "
# If the user however types stop, then the loop is broken and we jump down to the
# sum bit - where all of the numbers in the total_user_input array are added together
# and printed. End of program!
if user_input == "stop"
break
end
sum = 0
total_user_input.each { |num|
sum += num
}
puts sum
end
The output isn't as it should be.
As others have identified the problems with your code let me suggest how you might reorganize it. Ruby provides many ways to execute loops but you many find it desirable to primarily relay on the method Kernel#loop and the keyword break. (As you will learn in time, loop is particularly convenient when used with enumerators.)
def sum_numbers
tot = 0
loop do
print 'Gimme a number: '
s = gets.chomp
break if s == 'Stop'
tot += s.to_i
end
tot
end
The keyword break can optionally take an argument (though why that is not mentioned in the doc I cannot say), in which case it (if a literal) or its value (if a variable or method) is returned by loop. Here one would generally see
break tot if s == 'Stop'
without the final line, tot. As the loop returns tot and that is the last calculation performed by the method, the method will return the final value of tot.
You could have instead written
return tot if user_input == 'Stop'
but I think most coders believe best practice dictates that one should not return from a method from within a loop (or from within nested loops) unless there is a good reason for doing so.
Some small points:
I used print rather than puts to that the user's entry will be shown on the same line as the prompt.
I used s (for "string") rather than user_input because it reduces the chance of spelling mistakes (e.g., user_imput), speeds reading, and (possibly a foible of mine), looks neater. True, s is not descriptive, but one only has to remember its meaning for three consecutive lines of code. Others may disagree.
You could write, break if s.downcase == 'stop' if you want, say, 'stop' or 'STOP' to have the same effect as 'Stop'.
'23O3'.to_i #=> 23 (that's an an oh, not a zero), so in real life you'd want to confirm that either 'Stop' or the string representation of a number had been typed.
This is how I would do this preferring to use loop do end syntax with a break when it should. Also added a bit more text so user knows what's happening.
total_user_input = []
puts 'Give me a number or "stop" to end: '
loop do
user_input = gets.chomp
total_user_input << user_input.to_i
puts "Give me a number: "
break if user_input.downcase == "stop"
end
puts "Total entered: #{total_user_input.inject(&:+)}" unless total_user_input.empty?
puts 'goodbye!'
Note these few things:
get.chomp.to_i will convert every input to integer. ("stop" or any non integer string will be 0)
Arrangement of the flow is quite messy.
total_user_input = []
puts "Give me a number: "
while user_input = gets.chomp.strip
total_user_input.push(user_input.to_i)
sum = 0
total_user_input.each { |num|
sum += num
}
puts sum
if user_input == "stop"
break
end
end
Hope you understand this.

Ruby Guessing Game w 'Loop Do'

I created a guessing game through Ruby and I believe the structure of my code is off. When entering 'Cheat', you are given the random number then asked to type it in again. When typed in again, it says the random number is not correct and always defaults to my 'elseif' in line 45.
puts "Hey! I'm Sam. What's your name?"
name = gets
puts "Welcome #{name}. Thanks for playing the guessing game.
I've chosen a number between 1-100.
You'll have 10 tries to guess the correct number.
You'll also recieve a hint when you're guess is wrong.
If you feel like being a big ol cheater, type 'Cheat'.
Let's get started..."
random_number = rand(1...100)
Cheat = random_number
counter = 10
loop do
break if counter == 0
divisor = rand(2...10)
guess = gets.chomp
break if guess.to_i == random_number
counter -= 1
if
guess == random_number
puts 'You guessed the right number! You win!'
end
if counter < 4
puts "You can go ahead and cheat by typing 'Cheat'..."
end
if guess.to_s.downcase.eql? "cheat"
puts "The random number is #{random_number} you CHEATER!! Go ahead and type it in..."
guess = gets.chomp
puts = "You win cheater!"
end
if
guess.to_i < random_number
puts 'Ah shucks, guess again!'
guess = gets.chomp
elsif
guess.to_i > random_number
puts 'Too high, guess again!'
guess = gets.chomp
end
if random_number % divisor == 0
puts "Thats not it.\n #{guess} is #{guess.to_i > random_number ? 'less' : 'greater'} than the random number.
The random number is divisible by #{divisor}.\nTry again: "
elsif
puts "That's not the random number.\n #{guess} is #{guess.to_i > random_number ? 'less' : 'greater'} than the random number.
The random number is NOT divisible by #{divisor}.\nTry again: "
end
end
if counter > 0
puts "The number is #{random_number}! You win!"
else
puts "You lose! Better luck another time."
end
this is the response i get in the terminal
Let's get started...
Cheat
The random number is 96 you CHEATER!! Go ahead and type it in...
96
Thats not it.
96 is greater than the random number.
The random number is divisible by 8.
Try again:
The problem is here:
puts = "You win cheater!"
You're assigning the string "You win cheater!" to a local variable named puts. Changing it to this fixes the problem:
puts "You win cheater!"
You'll probably also want to put a break after that line.
As an aside, this pattern:
loop do
break if counter == 0
# ...
end
...would be better expressed as:
while counter > 0
# ...
end
...or:
until counter == 0
# ...
end
Also, you should always put the condition for an if/elsif/whathaveyou on the same line as if et al. Why? Because if you don't you get bugs like this:
if random_number % divisor == 0
# ...
elsif
puts "..."
end
Can you spot the bug? You forgot to put a condition after elsif, or used elsif when you meant to use else, which means that the return value of puts (which is always nil) is being used as the condition, just as if you had written elsif puts "...".
If you make a habit of always putting the condition on the same line as if/elsif, your eye will get used to it and errors like this will jump out at you.

Comparing user's input to multiple values

I am starting to learn programming and I have chosen to learn Ruby using Codecademy. However, I was trying to consolidate my learning but I just can't get this to work!
print "What is 2 + 2 ="
sum_var = gets.chomp
sum_var.downcase!
if sum_var == "four" || 4
puts "Correct!"
else sum_var != "four" || 4
puts "Wrong! #{sum_var} is not the answer!"
end
It just returns 'Correct!' even if it is wrong.
You need write the code as below :
print "What is 2 + 2 ="
sum_var = gets.chomp
# don't need to apply the bang method like you did - sum_var.downcase!
if sum_var.downcase == "four" || sum_var == '4'
puts "Correct!"
else # else don't need condition checking, so I removed.
puts "Wrong! #{sum_var} is not the answer!"
end
sum_var = gets.chomp gives you a string, no where you are converting it to a number. So, evenif you are passing number from the console it became "4" or "7" etc.
Let me explain you also why always you got "Correct!" as a output
sum_var == "four" || 4 - In this expression, whenever sum_var is not equal to "four", your first expression was evaluated to false, but when control went to test the second expression, it found there 4. You know in Ruby all objects are true, except nil and false. So 4 is considered as true. Thus in your code always if block was getting executed, and you were keep getting as the output "Correct!".
Now in your code, some other mess you did, that I corrected in my above code.
The problem with your code lies in the line
if sum_var == "four" || 4
The == will usually return false, so the second part will be evaluated because the precedence of == is higher than the precedence of ||. Since all objects except false and nil are "truthy" in Ruby the expression will end up being true regardless of the users input. Correction as proposed by #ArupRakshit, just wanted to add some more reason to it.

How to run a simple Ruby script

I would like to make a program that checks to see if the number you enter is an even number. Sort of like making a leap year program but for any number divisible by 2.
Something along the lines of:
num = gets.chomp
while num != 0
if (num%2) == 0
puts 'yess'
else
puts 'nooo'
end
end
I knows there's something easy that I need to change for it to run.
(btw I just started learning Ruby yesterday!)
There are two problems here.
First being something that others have put, you need to make sure you turn the input into an integer using ".to_i" on your num variable.
Secondly, this code puts you into an infinite loop since you are using a "while" loop.
Since the number is only input once, you get stuck in the "while" loop forever no matter what the input is. Basically, "num" never stops being not 0.
You'd be better off using an if..else statement. Something like:
num = gets.chomp.to_i
if num != 0
if (num%2) == 0
puts 'yess'
else
puts 'nooo'
end
else
puts "that's 0, dude"
end
Integers have two methods for this. They are even? and odd?.
You can use this in your if statement as so:
if num.even?
puts 'yess'
else
puts 'nooo'
end
However, an easier way to write this is with ternary expressions:
puts num.even? ? "yes" : "no"
However, make sure num is an Integer. Anything coming from gets will be a String. So, you should be doing num = gets.chomp.to_i. Anything that is not a number, like "h", will return 0.
"5".to_i #=> 5
"h".to_i #=> 0

Multiple conditions in a "While" loop Ruby

I'm creating an app that tells what month of the year each number from 1 to 12 represents, like 1 = January, 2 = February, etc. This is the initial code
print "Please, tell me the month's number!"
number = gets.chomp.to_i
while number > 12 do
print "Please, re-type it again!"
number = gets.chomp.to_i
end
case number
when 1 then print "This is January!"
when 2 then print "This is February!"
when 3 then print "This is March!"
when 4 then print "This is April!"
when 5 then print "This is May!"
when 6 then print "This is June!"
when 7 then print "This is July!"
when 8 then print "This is August!"
when 9 then print "This is September!"
when 10 then print "This is October!"
when 11 then print "This is November!"
when 12 then print "This is December!"
else print "I can't undestand you, i'm sorry!"
end
Now, the basic logic is set. The only thing that I think is missing is the second condition in the while loop that defines that, if the input isn't an integer, I need to retype it. I'm trying to define the second condition in that while loop correctly, but no results so far. How can I do it?
How can I make the code better? And is the while loop in this context the right method for the job?
Here is another way to make your code better:
require 'date'
print "Please, tell me the month's number! "
number = gets.to_i
until number.between?(1,12) do
print "Please, re-type it again! "
number = gets.to_i
end
month_name = Hash.new
(1..12).each do |month_num|
month_name[month_num] = Date::MONTHNAMES[month_num]
end
print "This is #{month_name[number]}!"
As noted in other answers chomp is not necessary if using to_i because converting to an integer will take care of the new line.
I am using number.between?(1,12) as suggested by #steenslag to check if the input is valid.
I tried a Hash approach instead of the case statement. A while loop is one way to do this but this is just one other way to get it done.
print "Please, tell me the month's number!"
number = gets.to_i
You never need chomp if you are going to use to_i because the very nature of a number states that it will never have a line ending.
until number > 1 && number < 12 do
You do want the number to be inclusive of 1 to 12, right?
You could alternative do this,
until (1..12).include?(number) do
Or as #teenslag states,
until number.between?(1, 12) do
I think either reads easier, and between? method may be the better choice.
print "Please, re-type it again!"
number = gets.to_i
end
This can be very DRY, or using the built in Date object, as mentioned. But let's see what we can do with the code as you have it, in the same spirit:
case number
when 1 then print "This is January!"
when 2 then print "This is February!"
when 3 then print "This is March!"
when 4 then print "This is April!"
when 5 then print "This is May!"
when 6 then print "This is June!"
when 7 then print "This is July!"
when 8 then print "This is August!"
when 9 then print "This is September!"
when 10 then print "This is October!"
when 11 then print "This is November!"
when 12 then print "This is December!"
end
turns to this:
answer = case number
when 1
"January"
when 2
"February"
when 3
"March"
when 4
"April"
when 5
"May"
when 6
"June"
when 7
"July"
when 8
"August"
when 9
"September"
when 10
"October"
when 11
"November"
when 12
"December"
end
print "This is #{answer}!"
Though it would be nicer just using the Date class.
If that wasn't available for you, then perhaps I would consider using a Hash here instead. I will let you explore those options.
Or even an Array. Remembering that Array elements start at position 0:
print "Please, tell me the month's number!"
until (number = gets.to_i).between?(1,12) do
print "Please, re-type it again!"
end
months = %w[January February March April May June July August September October November December]
puts "This is #{months[number - 1]}!"
number will always be an integer because you converted it into an integer.
"foo".to_i
=> 0
You probably want to use a range instead.
until (1..12).include?(gets.chomp.to_i)
print "Please, re-type it again!"
end
You can also DRY your code by using the built-in number to month conversion:
number = 4
Date::MONTHNAMES[number]
=> "April"
As the above posters have mentioned, this particular instance does not require a second condition. However, in the event you need a multiple condition while loop you would use the logical AND or the logical OR operator.
The logical AND operator is &&. The logical OR operator is ||. You would use && if both conditions need to be true to continue the loop. You'd use the || if one or the other would need to be true to continue the loop.
For example:
while number > 12 || number == 0 do
stuff...
end
while number > 0 && number < 13 do
stuff...
end
In the first snippet, you will enter the loop if the number entered is either above 12 OR equal to 0. In the second you will enter the loop if the number entered is greater than 0 AND less than 13. (Obviously, the second one would be exactly the opposite of what you were using the while loop for here, but is included for demonstration).
A simple loop that you need:
until month_num > 0 && month_num < 13 do
# prompt again
end
A more idiomatic way would be to use between? or include?
until month_num.between? (1, 12) do
# prompt again
end
And instead of multiple when then, you could use a hash map from the Date module:- Date::MONTHNAMES[month_num]

Resources