re-try logic in a simple loop - ruby

I'm trying to set bounds for an array that will later be printed in the console and summed. The lower bound ($a) should be less than 50 and I wrote this code to evaluate for that, but I want it to re-prompt for a number if a higher number is typed. So far, Google and experimentation have failed me.
def num_a
print "Pick a number from 1 to 50: "
$a = Integer(gets.chomp)
until $a < 50
puts "Um, try again please."
# need something here to prompt for another response
# until $a is less than 50
end
end

You could restructure the loop so that the prompt and call to gets are both inside it:
def num_a
# start with a number that doesn't meet the condition
a = 50
# check if the number meets the condition yet
until a < 50
# ask the user to enter a number
print "Pick a number from 1 to 50: "
a = Integer(gets.chomp)
# ask to try again if the number isn't under 50
puts "Um, try again please." unless a < 50
end
# return the entered value to the caller
a
end
Also, as I've shown in the example, I would recommend avoiding the use of global variables ($a in this case).

Related

Ruby while loop keeps repeating regardless of input

I wrote a simple guess the number game. But it keeps looping even when I input the correct number. Please help, thanks!
puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i
my_num = rand(831)
guess_count = 0
until user_guess == my_num do
if user_guess == my_num
guess_count += 1
puts "you got it!"
elsif user_guess <= 830
guess_count += 1
puts "higher"
else user_guess >= 1000
guess_count += 1
puts "lower"
end
end
puts "You guessed my number in #{guess_count} attempts. Not bad"
The part of the code that asks for a number from the user is outside the loop, so it will not repeat after the answer is checked. If you want to ask the user to guess again when their guess is wrong, that code needs to be inside the loop.
my_num = rand(831)
guess_count = 0
keep_going = true
while keep_going do
puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i
if user_guess == my_num
guess_count += 1
puts "you got it!"
keep_going = false
elsif user_guess <= 830
guess_count += 1
puts "higher"
else user_guess >= 1000
guess_count += 1
puts "lower"
end
end
puts "You guessed my number in #{guess_count} attempts. Not bad"
This code still has some bugs in it that stops the game from working correctly though, see if you can spot what they are.
As #Tobias has answered your question I would like to take some time to suggest how you might make your code more Ruby-like.
Firstly, while you could use a while or until loop, I suggest you rely mainly on the method Kernel#loop for most loops you will write. This simply causes looping to continue within loop's block until the keyword break is encountered1. It is much like while true or until false (commonly used in some languages) but I think it reads better. More importantly, the use of loop protects computations within its block from prying eyes. (See the section Other considerations below for an example of this point.)
You can also exit loop's block by executing return or exit, but normally you will use break.
My second main suggestion is that for this type of problem you use a case statement rather than an if/elsif/else/end construct. Let's first do that using ranges.
Use a case statement with ranges
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
guess_count += 1
case gets.chomp.to_i
when my_num
puts "you got it!"
break
when 0..my_num-1
puts "higher"
else
puts "lower"
end
end
There are a few things to note here.
I used print rather than puts so the user will enter their response on on the same line as the prompt.
guess_count is incremented regardless of the user's response so that can be done before the case statement is executed.
there is no need to assign the user's response (gets.chomp.to_i) to a variable.
case statements compare values with the appropriate case equality method ===.
With regard to the last point, here we are comparing an integer (gets.chomp.to_i) with another integer (my_num) and with a range (0..my_num-1). In the first instance, Integer#=== is used, which is equivalent to Integer#==. For ranges the method Range#=== is used.
Suppose, for example, that my_num = 100 and gets.chomp.to_i #=> 50 The case statement then reads as follows.
case 50
when 100
puts "you got it!"
break
when 0..99
puts "higher"
else
puts "lower"
end
Here we find that 100 == 50 #=> false and (0..99) === 50 #=> true, so puts "higher" is displayed. (0..99) === 50 returns true because the integer (on the right of ===) is covered by the range (on the left). That is not the same as 50 === (0..90), which loosely reads, "(0..99) is a member of 50", so false is returned.
Here are a couple more examples of how case statements can be used to advantage because of their reliance on the triple equality method.
case obj
when Integer
obj + 10
when String
obj.upcase
when Array
obj.reverse
...
end
case str
when /\A#/
puts "A comment"
when /\blaunch missiles\b/
big_red_button.push
...
end
Use a case statement with the spaceship operator <=>
The spaceship operator is used by Ruby's Array#sort and Enumerable#sort methods, but has other uses, as in case statements. Here we can use Integer#<=> to compare two integers.
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
case gets.chomp.to_i <=> my_num
when 0
puts "you got it!"
break
when -1
puts "higher"
else # 1
puts "lower"
end
end
In other applications the spaceship operator might be used to compare strings (String#<=>), arrays (Array#<=>), Date objects (Date#<=>) and so on.
Use a hash
Hashes can often be used as an alternative to case statements. Here we could write the following.
response = { -1=>"higher", 0=>"you got it!", 1=>"lower" }
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
guess = gets.chomp.to_i
puts response[guess <=> my_num]
break if guess == my_num
end
Here we need the value of gets.chomp.to_i twice, so I've saved it to a variable.
Other considerations
Suppose we write the following:
i = 0
while i < 5
i += 1
j = i
end
j #=> 5
j following the loop is found to equal 5.
If we instead use loop:
i = 0
loop do
i += 1
j = i
break if i == 5
end
j #=> NameError (undefined local variable or method 'j')
Although while and loop both have access to i, but loop confines the values of local variables created in its block to the block. That's because blocks create a new scope, which is good coding practice. while and until do not use blocks. We generally don't want code following the loop to have access to local variables created within the loop, which is one reason for favouring loop over while and until.
Lastly, the keyword break can also be used with an argument whose value is returned by loop. For example:
def m
i = 0
loop do
i += 1
break 5*i if i == 10
end
end
m #=> 50
or
i = 0
n = loop do
i += 1
break 5*i if i == 10
end
n #=> 50
1. If you examine the doc for Kernel#loop you will see that executing break from within loop's block is equivalent to raising a StopIteration exception.

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.

Generate largest number of 10 inputs

New to scripting here. Basically, I am needing to write a program to accept 10 input numbers and return the largest in the list.
I have this code, but obviously it is not running:
class Generator
def getNumbers
number = Array.new
puts "To begin, You will need to enter your numbers."
print "Press Enter to continue."
Console_Screen.pause
10.times do
print "\nPlease enter any number: "
input = STDIN.gets
input.chop!
list.push
return list
end
list.push(number)
print list.max
end
end
Console_Screen.new
Find = Generator.new
end
Can anyone help me with what I coded incorrectly?
There are many problems with your code. Among them I may point:
1) You created an array named number to store your numbers and then tried to push them to another variable named list;
2) As #tadman pointed, you used a return inside a times block and this makes the block to be executed only once;
3) You never invoked getNumbers to make your process really happen!
This code would do what you need:
class Generator
def get_numbers
numbers = Array.new
puts "To begin, You will need to enter your numbers."
puts
10.times do
print "\nPlease enter any number: "
input = STDIN.gets
input.chop!
numbers.push(input.to_i)
end
print numbers.max
end
end
Find = Generator.new
Find.get_numbers
Notice that I changed the name of your method from getNumbers to get_numbers, which is much more Ruby-like.
Notice that I also changed the name of your array from number to numbers, 'cause it will store numbers, not a single number. Naming your varables correctly may help you to think correctly about your problem. It also helps when it comes for other people reading your program. Even you, six months later, will have problems to understand your own code if you don't name things correctly an comment you code well.
By the way, I also pushed the numbers to numbers as integer, using #to_i. This will make the numbers be compared as numbers, not as strings. If you researsh a bit you'll find out this could be a bit different. If someone enters 0300 as a number, it will be considered to be smaller then 200 as string, but when converted to integers, they will be in the correct order.
Consider this approach perhaps:
#script.rb
nums = []
10.times do |c|
puts "enter number #{c+1}"
nums << gets.to_i
end
puts "max number is #{nums.max}"
Example
$ ruby script.rb
enter number 1
#5
enter number 2
#3
enter number 3
#66
enter number 4
#4
enter number 5
#3
enter number 6
#2
enter number 7
#1
enter number 8
#6
enter number 9
#9
enter number 10
#0
#max number is 66
I had the same assignment and this is what I ended up creating. (I had to get a little more involved with input control and I'm sure there's easier ways of doing this. Also had to utilize the variables as part of the grade. I assume to prevent me from ripping off the code above.)
class Screen
def cls
puts ("\n" * 25)
puts "\a"
end
def pause
STDIN.gets
end
end
Console_Screen = Screen.new
num = [] #array to store user input
$counter = 1
loop do
puts "Please enter number " + $counter.to_s + " of a series of 10 numbers in order to determine the largest."
# Console_Screen.pause
loop do
print "Enter Number Here:"
number = gets.chomp
num << number.to_i if number =~ /[0-9]/ #only writes to the array if it's a number
break if number =~ /[0-9]/ #only allow numbers
end
$counter += 1
break if $counter == 11
end
puts "Press Enter to have your numbers sorted and the largest presented to you."
Console_Screen.pause
largest = num.max
puts "This is your largest number: " + largest.to_s
Console_Screen.pause

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