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.