When I try to match for the string '3' in a case statement, it matches if the range goes up to '9', but not '10'.
I'm guessing it has something to do with the triple equals operator, but I don't know the exact reason why it can be in the range, but not matched.
Here is an IRB run documenting both cases that work (with '9'), and don't work (with '10'):
case '3'
when ('0'...'9')
puts "number is valid"
else
puts "number is not valid"
end
Output: number is valid
case '3'
when ('0'...'10')
puts "number is valid"
else
puts "number is not valid"
end
Output: number is not valid
The methods that I used as a reference for the expected results are Enumerable#include?
Enumerable#member?
and seeing what is output when converted to an array is (Enumerable#to_a
).
The result of the "case equality" (===
) operator surprised me.
puts ('0'...'10').include?('3')
# => true
puts ('0'...'10').member?('3')
# => true
puts ('0'...'10') === '3'
# => false
puts ('0'...'10').to_a
# => ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
Ranges use cover?
for case equality. So it is comparing '3' >= '0' && '3' < '10'
which results in false
because '3' < '10' #=> false
. Strings are compared based on character values.
For a better understanding you might want to see a string as an array of characters:
['3'] <=> ['1', '0'] #=> 1 (first operand is larger than the second)
To solve the issue convert your case input to an integer and use integer ranges:
case 3 # or variable.to_i
when 0...10
puts 'number is valid'
else
puts 'number is invalid'
end
This works because integers are not compared based on character code, but on actual value. 3 >= 0 && 3 < 10
results in true
.
Alternatively you could explicitly tell when to use the member?
(or include?
) method, by not passing a range, but a method instead.
case '3'
when ('0'...'10').method(:member?)
puts 'number is valid'
else
puts 'number is invalid'
end
===
says it's equivalent to cover?
, and the documentation for the latter states that it's equivalent to
begin <= obj < end
So, in your case, we're getting
'0' <= '3' < '10'
And <=
and <
on strings compare using dictionary order, so the comparison is false.
On the other hand, we have to do a bit more digging to figure out what member?
/ include?
actually do (the two are equivalent). If we look in the source code, we see that both invoke a function called range_include_internal
which has a special case for string arguments that behaves differently than cover?
. The latter calls rb_str_include_range_p
which has even more special cases, including your digit case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With