Reputation: 33
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!
Upvotes: 3
Views: 3839
Reputation: 434685
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 ..
.
Upvotes: 4
Reputation: 28606
If your example is not just an artificial MCVE for the general case but your actual problem, or if your actual problem is really similar, here are a few more ideas:
Treat as two independent problems, combine, handle the special case:
result = "#{roll1 < 4 ? 'Low' : 'High'} / #{roll2 < 2 ? 'Low' : 'High'}".
sub('High / High', 'JACKPOT!!')
Same idea written differently:
result = [roll1 < 4, roll2 < 2].
map { |low| low ? 'Low' : 'High' }.
join(' / ').
sub('High / High', 'JACKPOT!!')
Same again but a bit silly:
result = [roll1 < 4, roll2 < 2].
join(' / ').
gsub('true', 'Low').gsub('false', 'True').
sub('High / High', 'JACKPOT!!')
Using booleans because they just need ==
comparison:
result = case [roll1 > 3, roll2 > 1]
when [false, false]
"Low / Low"
when [true, false]
"High / Low"
when [false, true]
"Low / High"
when [true, true]
"JACKPOT!!"
end
(case [(4..6) === roll1, roll2 == 2]
would work as well. And note I indented differently.)
Combine the rolls into a single number:
result = case roll1 * (-1)**roll2
when -3..-1
"Low / Low"
when -6..-4
"High / Low"
when 1..3
"Low / High"
when 4..6
"JACKPOT!!"
end
Same idea written differently:
result = case roll2 * 10 + roll1
when 11..13
"Low / Low"
when 14..16
"High / Low"
when 21..23
"Low / High"
when 24..26
"JACKPOT!!"
end
Same again, just different code style:
result = case roll1 * (-1)**roll2
when -3..-1 then "Low / Low"
when -6..-4 then "High / Low"
when 1..3 then "Low / High"
when 4..6 then "JACKPOT!!"
end
Upvotes: 0
Reputation: 110685
Except in the case of “JACKPOT!!”
you have two separate problems, which are easier to deal with by treating them separately.
def result_of_rolls(roll1, roll2)
if (4..6).cover?(roll1) && roll2 == 2
"JACKPOT!!"
else
"%s / %s" % [(1..3).cover?(roll1) ? "Low" : "High",
roll2 == 1 ? "Low" : "High"]
end
end
result_of_rolls(2,1) #=> "Low / Low"
result_of_rolls(4,1) #=> "High / Low"
result_of_rolls(3,2) #=> "Low / High"
result_of_rolls(5,2) #=> "JACKPOT!!"
If there were three or more rolls, rather than just two, it can be seen that this approach would be much more efficient than one that examined all the combinations of values of roll1
, roll2
, roll3
, and so on.
Upvotes: 0
Reputation: 28606
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
Upvotes: 4
Reputation: 84373
The when-statement doesn't really work the way you seem to think, and neither does an array literal. In its basic form, case
compares a top-level expression to the expressions in a when statement using the threequals operator (===
).
In Ruby, almost everything is an expression. In your example, you're trying to match an array of values to an array that resolves to a single-element array. Consider:
[1..3 && 2]
#=> [1..2]
[1..3 && 2].count
#=> 1
[1..3 && 2].map &:class
#=> [Range]
Basically, your top-level array never matches any of the array expressions you're providing as conditions. What you want is probably something like this:
result = case
when (1..3).include?(roll1) && roll2.eql?(1)
"Low / Low"
else
raise "invalid comparison"
end
In this type of construction, you're not using a threequals expression with a top-level value to compare against. Instead, you're constructing a truthy or falsey Boolean from a pair of expressions. Flesh this out with additional when statements, and then debug your expressions if you're still getting invalid comparisons.
Upvotes: 2