How can I avoid error when setting elsif range condition? - ruby

def Summer
#summer = true
puts "Your fruit are ripe for the picking."
if #tree_age == 1..5 && #tree_age > 0
#oranges = 5
elsif #tree_age == 6..15
#oranges = 20
else
#oranges = 50
end
end
I'm trying to ensure a tree between a certain age range gives x oranges, however I'm stuck with the following error referring to my elsif statement:
Orange_tree.rb:14: warning: integer literal in conditional range
I have also tried using an if greater than && less than conditional statement, can somebody please explain what this error means, and how to reach my solution.

You have a few problems:
You'll want to put your ranges in parenthesis when other operators or methods are nearby. Your current error comes from Ruby parsing elsif #tree_age == 6..15 differently than you expect - it's treating it as (1 == 6)..15, and false..15 obviously doesn't make any sense.
To test a number is within a range, use (1..5) === num, not num == (1..5). Range#=== is defined to test that the Range includes the right hand side, while Fixnum#== and Fixnum#=== both just test that the right hand side is numerically equivalent.
You don't need to test #tree_age > 0. You're already testing that it's in 1..5.
You could also consider a case statement for this, which can be a bit easier to read. case does its comparisons using ===.
#oranges = case #tree_age
when 1..5 then 5
when 6..15 then 20
else 50
end

You should use include? instead of == to determine if the given number is within the range:
def Summer
#summer = true
puts "Your fruit are ripe for the picking."
if (1..5).include?(#tree_age) && #tree_age > 0
#oranges = 5
elsif (6..15).include? #tree_age
#oranges = 20
else
#oranges = 50
end
end
==:
Returns true only if obj is a Range, has equivalent begin and end
items (by comparing them with ==), and has the same exclude_end?
setting as the range.
Which is obviously not the case.

The problem is with the lines that say == with a range.
if ( 10 == 1..11) # throws integer literal in conditional range warning
puts "true"
end
If you did this instead
if ( 10.between?(1, 11))
puts "true"
end

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.

How to use a random number generator to determine path in "if" statement

I'm trying to use a random number to determine if an event will happen using an if statement.
I keep getting the following error:
lunarlander.rb:13: syntax error, unexpected '='
if (numb % = 2)*
Here is the code:
def space_travel
puts "\n"
puts "You engage the main thrusters and you feel the ship jerk forward."
numb = rand(10)
if (numb % == 2)
puts "Everything functions as expected. You settle in for the trip."
else
spacewalk()
end
end
You need the second argument for modulo operator (%). Now you have
numb % == 2
and you need to insert a number (or a numeric variable) between modulo and equals, e.g.
numb % 5 == 2

Combining multiple 'elsif' statements

I try to programm snakes in Ruby. In order to get myself more familiar with Ruby. I define the position of every part of the snake through saving its X and Y value in two 1D arrays one for a X value and one for a Y value.
$x = [2,...]
$y = [2,...]
(What I forgot to tell is that the head of the Snake moves through user input while the rest just inherits its position from the part before like this.)
def length(f)
if $length >= f
$y[f] = $y[f-1]
$x[f] = $x[f-1]
end
end
In order to get a field for the Snake to move around I programmed this.
for a in (1..20)
for b in (1..20)
print " X "
end
puts" "
end
Which gives me a 20*20 field.
I then tried to display every part of the snake like on the field like this.(While also drawing a boarder around the field.)
for a in (1..20)
for b in (1..20)
if a == 1 || a == 20
if b == 1 || b == 20
print " + "
else
print " - "
end
elsif b == 1 || b == 20
print " | "
elsif a == $x[0] && b == $y[0]
body
elsif a == $x[1] && b == $y[1]
body
elsif a == $x[2] && b == $y[2]
body
elsif a == $x[3] && b == $y[3]
body
elsif a == $x[4] && b == $y[4]
body
else
print " "
end
end
puts""
end
This works but if the user is really good/ has a lot of spare time I need to make allot of elsif for every one represents a part of the snake if the snake should have as a limit a length of 100 I would need to make 100 elsif statements.(The body is just:
def body
print " # ".green
end
)
I tried fixing it with a for loop like this:
for c in (1..100)
if a == $x[c] && b == $y[c]
body
end
end
and this
loop do
$x.size.times do |index|
if $x[index] == a && $y[index] == b
body
end
end
break
end
But sadly this didn't gave the desired outcome for this interfered with the ifthat draws the boarders of the field.
Is there a way to combine these multiple elsif statements?
Every help would be highly appreciated. ( Sorry for being to vague in the first draft.)
Recommended Refactorings
NB: You included no sample data in your original post, so your mileage with answers will vary.
You have a number of issues, not just one. Besides not being DRY, your code is also not very testable because it's not broken out into discrete operations. There are a number of things you can (and probably should) do:
Break your "body" stuff into discrete methods.
Use Array or Enumerator methods to simplify the data.
Use dynamic methods to loop over your arrays, rather than fixed ranges or for-loops.
Use case/when statements inside your loop to handle multiple conditionals for the same variable.
In short, you need to refactor your code to be more modular, and to leverage the language to iterate over your data objects rather than using one conditional per element as you're currently doing.
Simplify Your Data Set and Handle Procedurally
As an example, consider the following:
def handle_matched_values array
end
def handle_mismatched_values array
end
paired_array = a.zip b
matched_pairs = paired_array.select { |subarray| subarray[0] == subarray[1] }
unmatched_pairs = paired_array.reject { |subarray| subarray[0] == subarray[1] }
matched_pairs.each { |pair| handle_matched_values pair }
matched_pairs.each { |pair| handle_mismatched_values pair }
In this example, you may not even need an if statement. Instead, you could use Array#select or Array#reject to find indices that match whatever criteria you want, and then call the relevant handler for the results. This has the advantage of being very procedural, and makes it quite clear what data set and handler are being paired. It's also quite readable, which is extremely important.
Dynamic Looping and Case Statements
If you truly need to handle your data within a single loop, use a case statement to clean up your conditions. For example:
# Extract methods to handle each case.
def do_something_with data; end
def do_something_else_with data; end
def handle_borders data; end
# Construct your data any way you want.
paired_array = a.zip b
# Loop over your data to evaluate each pair of values.
paired_array.each do |pair|
case pair
when a == b
do_something_with pair
when a == paired_array.first || paired_array.last
handle_borders pair
else
do_something_else_with pair
end
end
There are certainly plenty of other ways to work pairwise with a large data set. The goal is to give you a basic structure for refactoring your code. The rest is up to you!
I would start with something like this:
(1..20).each do |a|
(1..20).each do |b|
if [1, 20].include?(a)
print([1, 20].include?(b) ? ' + ' : ' - ')
elsif (1..100).any? { |i| a == $x[i] && b == $y[i] }
body
else
print(' ')
end
puts('')
end
end
I suppose this would work as a solution even if it is not that advanced?
loop do
$x.size.times do |index|
if $x[index] == a && $y[index] == b
body
end
end
break
end

Beginner Ruby if else

I've check to see if the program is recieving number and names and it is. I'm wondering why doesn't it print "fred" when input number is 1?
number = ARGF.read.chomp
names = %w{fred betty barney}
if number == 1
puts names[0]
elsif number == 2
puts names[1]
elsif number == 3
puts name[2]
end
number is likely a string here, but you are comparing it with an integer.
1 == '1' # false
try
number = ARGF.read.chomp.to_i # note the to_i here
names = %w{fred betty barney}
if number == 1
puts names[0]
elsif number == 2
puts names[1]
elsif number == 3
puts names[2]
end
Also, you can use a case/when statement when you want to take a different path based on multiple values of a single variable. This is usually the cleaner way to handle this type of flow.
number = ARGF.read.chomp.to_i
names = %w{fred betty barney}
case number
when 1
puts names[0]
when 2
puts names[1]
when 3
puts names[2]
end
Or in this case, the even more simple:
number = ARGF.read.chomp.to_i
names = %w{fred betty barney}
puts names[number-1]
should work.
I know you're learning if...else, but keep in mind you can also do this:
number = ARGF.read.chomp.to_i - 1
names = %w{fred betty barney}
puts names[number]
number is a string, not an integer. You can either convert to an integer:
number = ARGF.read.chomp.to_i
or you can test against strings instead:
if number == "1"
...
...
...
The number is a string. You can check that that by printing its class like this:
p number.class
You need to convert number to an integer like this:
number = ARGF.read.chomp.to_i
Keep in mind though that to_i would return 0 for invalid string. Do it only when you are sure about the incoming data.
Try this on Codepad.

Why doesn't "case" with "when > 2" work?

Why is this not working?
case ARGV.length
when 0
abort "Error 1"
when > 2
abort "Error 2"
end
It's not valid ruby syntax.
What you need is
case
when ARGV.length == 0
abort "Error 1"
when ARGV.length > 2
abort "Error 2"
end
When you write case x, the important part you need to understand is that ruby takes the x and then applies a comparison to the argument or expressions you insert in the when clause.
The line where you say when x >2 reads to ruby like:
if ARGV.length == > 2
When you remove a specific object from the case statements, you can apply conditionals within the when statements .
Use 1.0 / 0.0 to get infinity, which fixes #mosch's code:
case ARGV.length
when 0
raise "Too few"
when 3..(1.0/0.0)
raise "Too many"
end
You don't have to be Chuck Norris to divide by a floating point zero.
Well, it doesn't work because it's not valid ruby syntax. However, you can do this:
x = 15
case x
when 0..9 then puts "good"
when 10..12 then puts "better"
when 13..200 then puts "best"
else
puts "either great or poor"
end
An if statement would probably be more fitting for your code, since you don't have a definitive range/value, but rather just a greater-than:
if ARGV.length == 0
abort "Error 1"
elsif ARGV.length > 2
abort "Error 2"
end
You can use either if elsif or lambda/proc (then notation is just for shortness):
case x
when 1 then '1'
when ->(a) { a > 2 } then '>2'
end
You can also create a named variable for clarity:
greater_than_2 = ->(a){ a > 2 }
case x
when 1 then '1'
when greater_than_2 then '>2'
end
You can also create your own comparison predicate:
greater_than = ->(n) { ->(a) {a > n} }
case x
when 1 then '1'
when greater_than[2] then '>2'
when greater_than[5] then '>5'
end
Answering your initial question when > 1 is not a valid syntax, since > is binary operator in Ruby, which is to say its arity 2, meaning it takes 2 arguments. Don't be confused of term binary vs bitwise.

Resources