How to use .include? to capture multiple options resulting in same operation - ruby

I am writing a simple calculator that asks the user what calculation she wants to perform with a choice between '+', '-', '*' and '/'.
The code works when I separate each option with ||. However, I would prefer to use .include? to avoid repetition.
while play_again != "N"
puts "Enter a first number:"
first_num = gets.chomp.to_i
puts "Enter a second number:"
second_num = gets.chomp.to_i
puts "Which operation would you like to perform? '+', '-', 'x', '/'"
operator = gets.chomp
if operator == "+" || operator == "-" || operator == "x" || operator
== "/"
puts calculator(first_num, second_num, operator)
else
puts "That wasn't a valid selection."
end
puts "Do you want to calculate something else? (Y/N)"
play_again = gets.chomp
end
I tried to rewrite the line starting with if operator with if operator.include?(%w[+ - X /].to_s) but this doesn't capture the user input properly and always puts " That wasn't a valid selection."

In this way
if %w(+ - * /).include?(operator)
a shorter way for this one
if ["+", "-", "*", "/"].include?(operator)
This makes sense, don't you think? You're checking that the operator is included in some options. In this case the options are four strings, the symbols for the four basic mathematical operations.

A case when control structure lets you specify multiple conditionals. Sounds hard, is actually easy:
puts case operator
when '+', '-', 'x', '/' then calculator(first_num, second_num, operator)
else "That wasn't a valid selection."
end

Related

My Ruby calculator program acts like I didn't enter a valid operator

I have started learning Ruby today and so far I am loving the easy to read syntax! However, I have made a calculator app to learn the language and am struggling to get it to work. Below is the code for the app.
def add(first_number, second_number)
first_number + second_number
end
def subtract(first_number, second_number)
first_number - second_number
end
def multiply(first_number, second_number)
first_number * second_number
end
def divide(first_number, second_number)
first_number / second_number
end
def mod(first_number, second_number)
first_number % second_number
end
def main()
puts "First number: "
first_number = gets
puts "Operator (+, -, *, /, %): "
operator = gets
puts "Second number: "
second_number = gets
if operator == "+"
result = add(first_number, second_number)
elsif operator == "-"
result = subtract(first_number, second_number)
elsif operator == "*"
result = multiply(first_number, second_number)
elsif operator == "/"
result = divide(first_number, second_number)
elsif operator == "%"
result = mod(first_number, second_number)
else
result = "Invalid operator. Please try again."
end
puts "Result: " + result
end
main()
It keeps going to the else and printing invalid operator and I am unsure why. Am I making a stupid mistake, or am I going about this program the wrong way? Thank you in advance and sorry for such a simple question, I have only started learning Ruby today :)
The answer is quite simple: gets isn't doing what you think. It's preserving the trailing newline.
irb(main):003:0> gets
hello
=> "hello\n"
You need to get rid of the trailing newline.
irb(main):004:0> gets.chomp
hello
=> "hello"
You can see the impact of this on comparisons:
irb(main):013:0> gets.chomp == "hello"
hello
=> true
irb(main):014:0> gets == "hello"
hello
=> false
If you want to get rid of all leading or trailing whitespace, you may want to use #strip.
irb(main):015:0> gets.strip
+
=> "+"
You'll also need to convert your inputs for operands to numbers.
first_number = gets.to_f
When converting this way, there is no need to chomp off the trailing newline, though you could: gets.chomp.to_f.
This program is way longer and more complex then it needs to be. In Ruby operators are just implemented as methods so you can use Object#send to perform all of these simple mathematical operations:
# This is Ruby - not C/Java/Rust etc - there is no need for a "main" method.
puts "First number: "
first_number = gets.chomp.to_f
# Get the user input in a loop until the user provides a valid operator
operator = loop do
puts "Operator (+, -, *, /, %): "
operator = gets.chomp
if ["+", "-", "*", "/", "%"].include?(operator)
break operator
else
puts "Invalid operator. Please try again."
end
end
puts "Second number: "
second_number = gets.chomp.to_f
puts "Result: " + first_number.send(operator, second_number)
If you want to be able to take more advanced input and map it to methods in this way using a lookup table (a hash) as suggested by Tadman is a good idea.

How do I check if user input is an integer in Ruby?

I am trying to loop until user inputs an integer. When user inputs a letter, the following code should print "Think of a number":
print "Think of a number "
while user_input = gets.to_i
if user_input.is_a? Integer
puts "your number is #{user_input}"
break
else
print "Think of a number "
end
end
I succeeded with my code when user inputs an integer. However when user inputs a string, the to_i method returns 0, and does not execute the else statement because it is a number.
The main issue with your code is String#to_i method is omnivorous.
"0".to_i #⇒ 0
"0.1".to_i #⇒ 0
"foo".to_i #⇒ 0
That said, user_input in your code is always integer.
What you probably want is to accept digits only (and maybe a leading minus for negatives.) The only concise way to accept a subset of characters is a regular expression.
# chomp to strip out trailing carriage return
user_input = gets.chomp
if user_input =~ /\A-?\d+\z/
...
The regular expression above means nothing save for digits with optional leading minus.
Or, even better (credits to #Stefan)
if gets =~ /\A-?\d+\Z/
If you only want to accept postive digits, you can use a range:
user_input = gets.chomp
if ('0'..'9').cover? user_input
let check below one used Integer(gets.chomp) rescue ''
print "Think of a number "
while user_input = Integer(gets.chomp) rescue ''
if user_input.is_a? Integer
puts "your number is #{user_input}"
break
else
print "Think of a number "
end
end
I came across a similar problem. I ended up doing this:
if user_input.strip == user_input.to_i.to_s
# More code here!
end
Testing for float would be:
if user_input.strip == user_input.to_f.to_s
# More code here!
end
Solved my issue. See if it helps.

How to check if an array includes either X or Y

My assignment is to check whether a given input by the user contains the letters "c" or "s". I managed with one but, I simply don't know the correct way to write that.
I know that the problem is "s" || "c".
print 'What can we do for you?'
user_input = gets.chomp
user_input.downcase!
if user_input.empty?
puts 'Well you will have to write something...!'
elsif user_input.include? 's' || 'c'
puts "We got ourselves some 's's and some 'c's"
user_input.gsub!(/s/, 'th')
user_input.tr!('c', 's')
puts "The Daffy version, #{user_input}!"
else
print "Nope, no 's' or 'c' found"
end
simply
elsif user_input.include?("s") || user_input.include?("c")
or something like
%w(s c).any? { |command| user_input.include? command }
This is the perfect example of where regular expressions are fine:
user_input =~ /[sc]/
or:
(user_input.split('') & %w(s c)).any?
You can use Regexp
user_input[/s|c/]
No regexp:
user_input.count('sc') > 0

how to meet a condition before going on to the next step?

how do I loop if a condition is not met?
print "Please enter first number "
first_number = gets.chomp
if first_number =~ /[a-zA-Z]/
puts "not a number"
end
As per the code posted above, if you enter a letter, you'll get the statement of it not being a number.
How do I repeat it, if a user enters a letter?
As of now, it goes to the next one which is this:
print "Please enter second number "
second_number = gets.chomp
if second_number =~ /[a-zA-Z]/
puts "not a number"
end
I don't want it to it to go to the next one, until the user has entered a number in the first one.
You can use while and until as a modifier to a block. This will run the block first and then check a conditional and run the block again until it passes, which is the behavior you want.
begin
puts 'Please enter first number'
first_number = gets.chomp
end until first_number =~ /\d+/
I suggest you consider doing it like this:
num = nil # initialize to anything
loop do
puts 'Enter a number'
num = gets.chomp.strip
case num
when /^\d+$/
break
when /^[a-z]+$/i
print "You entered one or more letters and no digits."
else
print "You made some other illegal entry."
end
puts " Try again"
end
puts "You enterered the number #{num}"
Some notes:
num must be initialized (to anything) before the loop in order for it to be visible after the loop's end statement.
the case statement, since it uses === for evaluating when expressions, allows you to enter a regex for each case.
^ and $ in the regexes are anchors, so that, for example, "34b" =~ /^\d+$/ => nil (what I assume you want), rather than "34b" =~ /\d+/ => 0.
the i in /[a-z]+$/i allows matching letters to be uppercase or lowercase.
the user may enter one or more digits, or one or more letters, but there are many other possibilities as well ("3$,a"), so I added another "catch-all" possibility in the case statement.

Optional digits in a sum?

This is my first programming language so please bear with me!
I can't quite figure out where it's going wrong. I'm not necessarily asking for a solution as this is a learning exercise; I just need a helping hand as to where I should be looking.
#Calculate the sum of two numbers and an optional third
#get first number
print "Please enter your first digit: "
value_1 = gets.chomp
print value_1
#get second number
print "Please enter your second digit: "
value_2 = gets.chomp
#get the additional number
print "Do you want to add an additional number?"
add_num_req = gets.chomp
#calculate result and put
if gets.chomp = "yes" || "Yes" || "YES"
print "Please enter the additional digit: "
add_num_1 = gets.chomp
#print sum of three values
print "Answer: " , (value_1.to_i + value_2.to_i + add_num_1.to_i), "\n";
else
#print value_1 + value_2
print "Answer: " , (value_1.to_i + value_2.to_i), "\n";
end
But this produces a blank return after putting in the response to the get.chomp for an additional digit. Any help?
As a fourth alternative (and what I usually use) ...
if gets.chomp.downcase == "yes"
As with the regex match, it also accepts unexpected case arrangements (e.g. "yEs", "yES", "YeS" and so on)
In Ruby you can't compare a variable to many options as you have there. You have to do something like this:
if add_num_req == "yes" || add_num_req == "Yes" || add_num_req == "YES"
Another way to do it is to take advantage of the Enumerable module. But this is a little more advanced, although you will find it useful as you continue to use Ruby.
answers = ["yes", "Yes", "YES"]
if answers.any? { |e| add_num_req == e }
change:
if gets.chomp = "yes" || "Yes" || "YES" #you are using = instead of == which is equality
to:
if gets.chomp.match(/yes/i) #This is a case-insensitive regex to match "yes", "Yes" or "YES"
A third alternative:
if ( ['yes','Yes','YES'].include?(add_num_req) )
...
Here's a more Ruby-like way to write your program:
def doit
value_1 = obtain_entry("Please enter your first digit: ").to_i
value_2 = obtain_entry("Please enter your second digit: ").to_i
loop do
case obtain_entry("Do you want to add an additional digit?: ")
when "yes", "Yes", "YES"
print "Please enter the additional digit: "
puts "Answer: #{value_1 + value_2 + gets.to_i}"
break
when "no", "No", "NO"
puts "Answer: #{value_1 + value_2}"
break
end
end
end
def obtain_entry(str)
print str
gets.chomp
end
doit
A few points:
Since you are getting a response from the user more than once, put that in a method to which you pass the question to be asked (here, obtain_entry). For answers that are to be treated as integers, you may as well convert them to integers when they are returned. (In a real application you would of course want to make various checks on the type and reasonableness of answers.)
Do not define variables when it is not necessary to do so. For example, I've not created a variable for the reply to either of the questions "Do you want to add an additional digit?: " and "Please enter the additional digit: ".
I've added an endless loop that ensures that an acceptable "yes" or "no" answer is given to the question "Do you want to add an additional digit?. If an acceptable answer is given, we break out of the loop; if not, the question is repeated and the user is given another opportunity to answer.
It is often convenient to use a case statement in the way I have done when several replies result in the same action being taken.
When displaying output, I've used the more conventional way of forming a string, using string interpolation (e.g., #{variable_x}), as in puts "Answer: #{value_1 + value_2 + gets.to_i}". You must use double quotes for string interpolation to work.
When using IO#gets to obtain a string that is to be converted to an integer, String#chomp is not needed; gets.to_i is sufficient.
My guess is your problem is happening here:
#get the additional number
print "Do you want to add an additional number?"
add_num_req = gets.chomp
#calculate result and put
if gets.chomp = "yes" || "Yes" || "YES"
Since you are calling gets.chomp twice for the same input and using an assignment operator = in place of comparison operator ==. Also, as someone else has pointed out, each || operator should evaluate a Boolean expression, e.g. add_num_req == 'yes' || add_num_req == 'YES'. Without modifying your code too much, I think you want something like this:
print "Do you want to add an additional number? "
add_num_req = gets.chomp
#calculate results and put
if add_num_req.downcase == 'yes'
# ...
On that note, if you plan to be evaluating a lot of strings, regular expressions are invaluable. I still can't write a decent regexp without checking a reference, but even so they make a world of difference!

Resources