Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using 'case' with multiple conditions

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!

like image 441
George Tucker Avatar asked May 23 '26 21:05

George Tucker


2 Answers

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 ...

like image 89
mu is too short Avatar answered May 26 '26 12:05

mu is too short


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

like image 36
Stefan Pochmann Avatar answered May 26 '26 12:05

Stefan Pochmann



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!