I'm trying to write a case statement which looks at two conditions, like this:
roll1 = rand(1..6)
roll2 = rand(1..2)
result = case[roll1, roll2]
when [1..3 && 1]
"Low / Low"
when [4..6 && 1]
"High / Low"
when [1..3 && 2]
"Low / High"
when [4..6 && 2]
"JACKPOT!!"
end
puts result
I'd love to get this working. I'd prefer to understand why my example fails.
Edited to add:
Thanks for all the feedback! Inspired, I realized that combining the two case variables allows me to collapse them into a single value for a simple switch statement...
roll1 = rand(1..6)
roll2 = rand(1..2)
if roll2 == 1
roll2 = 10
elsif roll2 == 2
roll2 = 20
end
result = case(roll1 + roll2)
when 11..13
"Low / Low"
when 14..16
"High / Low"
when 21..23
"Low / High"
when 24..26
"JACKPOT!!"
end
puts result
While this solves my immediate problem, it doesn't advance my underlying knowledge -- it's a trifling insight compared to all the awesome feedback I've received. Sincere thanks!
You have two problems with your code. First of all, this:
[1..3 && 1]
is an array with one element. Since .. has lower precedence than &&, you're really writing 1..(3 && 1) which is just a complicated way of saying 1..1. That means that your case is really:
case[roll1, roll2]
when [1..1]
"Low / Low"
when [4..1]
"High / Low"
when [1..2]
"Low / High"
when [4..2]
"JACKPOT!!"
end
The second problem is that Array doesn't override the === operator that case uses so you'll be using Object#=== which is just an alias for Object#==. This means that your case is equivalent to:
if([roll1, roll2] == [1..1])
"Low / Low"
elsif([roll1, roll2] == [4..1])
"High / Low"
elsif([roll1, roll2] == [1..2])
"Low / High"
elsif([roll1, roll2] == [4..2])
"JACKPOT!!"
end
[roll1, roll2] will never equal [some_range] because Array#== compares element by element and roll1 will never == a range; furthermore, you're also comparing arrays with different sizes.
All that means that you have a complicated way of saying:
result = nil
I'd probably just use an if for this:
result = if (1..3).include?(roll1) && roll2 == 1
'Low / Low'
elsif (4..6).include?(roll1) && roll2 == 1
'High / Low'
...
or you could use === explicitly:
result = if (1..3) === roll1 && roll2 == 1
'Low / Low'
elsif (4..6) === roll1 && roll2 == 1
'High / Low'
...
but again, watch out for the low precedence of ...
As the other answers explain in more detail, your when [1..3 && 2] doesn't work because that's actually when [1..2] and because arrays don't compare their elements with === (which when does and which the range would need to do).
Here's another way to make it work, by fixing exactly those two issues.
First, use [1..3, 2] instead of [1..3 && 2], so the two conditions don't get combined but stay separated in the array. Then, to get === used, create a subclass of Array that compares elements with === instead of ==. And use it in the when condition instead of a normal array. Full code:
roll1 = rand(1..6)
roll2 = rand(1..2)
class Case < Array
def ===(other)
zip(other).all? { |x, y| x === y }
end
end
result = case[roll1, roll2]
when Case[1..3, 1]
"Low / Low"
when Case[4..6, 1]
"High / Low"
when Case[1..3, 2]
"Low / High"
when Case[4..6, 2]
"JACKPOT!!"
end
puts roll1, roll2, result
That for example prints:
6
2
JACKPOT!!
I guess whether this is good / worth it for you depends on your actual use case. But I like it. And as a Ruby beginner myself, this little exercise helped me understand better how when and === work.
Also see the discussion under @muistooshort's answer for some thoughts about this.
And this answer about what === does was also very illuminating:
https://stackoverflow.com/a/3422349/1672429
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