-1

I want someone to enter number "1", "2", or "3", and if the number is correct, want to say "ok". If not, I want to say "enter 1 or 2 or 3".

This is my code:

puts "enter 1 or 2 or 3"
num = gets.to_i

if num == 1 or 2 or 3
  puts "ok"
else
  puts "enter 1 or 2 or 3"
end

When I enter an incorrect answer such as "e" or "p", it still says "ok".

Why is it the case?

sawa
  • 165,429
  • 45
  • 277
  • 381
RITA
  • 27
  • 4

2 Answers2

5

Let's first examine why you are obtaining incorrect results.

If a equals false or nil (is falsy, meaning logically false), a or b returns the value of b. If a equals any other value (is truthy, meaning logically true), a or b returns a.

Suppose we have an expression a op1 b op2 c, where op1 and op2 are operators (e.g., a == b or c). This could be evaluated (a op1 b) op2 c or a op1 (b op2 c), where parentheses have the highest precedence.

The precedence of Ruby's operators (most implemented as methods) is given here. Note that == has higher precedence than or. Moreover, for any given operator op, a op b op c is evaluated (a op b) op c.

The expression num == 1 or 2 or 3 is therefore evaluated

((num == 1) or 2) or 3

Now consider the value of this expression, depending on the value of num.

num = 1
((num == 1) or 2) or 3 => (true or 2) or 3 => true or 3 => true

num != 1
((num == 1) or 2) or 3 => (false or 2) or 3 => 2 or 3 => 2

Here are some ways to obtain your desired result.

(num == 1) or (num == 2) or (num == 3)
(num == 1) || (num == 2) || (num == 3)
[1, 2, 3].include?(num)
[1, 2, 3] & [num] == [num]
([num] - [1, 2, 3]).empty?

Because of the precedence rules for operators, the parentheses are not needed in the first two expressions, but it can be argued they clarify the code, at least for some readers. (I would include them.)

Regarding the choice between using or or ||, see this SO queston, particularly the second answer. In practice, or is rarely used.

See Array#include?, Array#& and Array#-.

To round out the possibilities, one could conceivably use a case statement.

case num
when 1, 2, 3 then true
else false
end

If, as here, the acceptable values of num form a range, one could write either of the following.

num.between?(1, 3)
(1..3).cover?(num)
(num >= 1) && (num <= 3)

See Comparable#between and Range#cover?. Again, the parentheses in the latter are optional.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Unlike every other example in this well-written answer, `Range#cover` is different in that it will return `true` for non-integers. For example, `(1..3).cover?(2.5)` returns `true`. You could also use `num.between?(1, 3)` to achieve the same result. – moveson Jan 28 '18 at 20:13
  • @moveson, good points, but of course `num = gets.to_i` will be an integer (even `'cat\n'.to_i #=> 0`). Some data checks may be needed for `str = gets` prior to the conversion of `str` to an integer. I added `num.between?` to my answer. All three range-related expressions return `true` when `num = 2.5`. – Cary Swoveland Jan 28 '18 at 21:11
  • 1
    You are correct as usual, sir! I just wanted to point it out in the event this answer is reviewed by someone looking at different ways to solve the problem of multiple equality comparisons, when the inputs may not be so clearly defined. With three or more elements, it feels to me like `[elements].include?(input)` has the edge from a usability and readability perspective. – moveson Jan 29 '18 at 14:29
1

In your code, num == 1 or 2 or 3 evaluates to true always, as 2 is considered logically true, and using an or`` operator with logically true value returns atrue` result always.

The correct way to compare is like this

puts "enter 1 or 2 or 3"
num = gets.to_i

if num == 1 or num == 2 or num == 3
    puts "ok"
else
    puts "enter 1 or 2 or 3"
end

Here, you are comparing the value of variable with right literal.

sawa
  • 165,429
  • 45
  • 277
  • 381
Harshit Garg
  • 2,137
  • 21
  • 23