Is it possible to pattern match on a hash value to get the hash key in Ruby 3?

832 views Asked by At

This is a question about pattern matching in Ruby 3.

I have a hash:

h = {
  x: [1, 2, 3],
  y: [11, 12, 13],
  z: [100, 101],
}

Given an integer (for example, 13), I'd like find the hash key whose hash value contains the integer (:y in the example).

Of course, there're ways to do this in Ruby without using pattern matching.

Here, I'm ONLY interested in using pattern matching of Ruby 3 on the hash to achieve this. Typically, pattern matching in Ruby matches on the keys of a hash, not on the values. Also, this hash has many keys and we don't know in advance the key names.

The nearest thing I've found is by converting the hash into an array first and using pattern matching on the array.

def find_the_key(a_hash, a_number)
  case a_hash.to_a
  in [*, [the_key, [*, ^a_number, *]], *]
    the_key
  else
    :key_not_found
  end
end

puts find_the_key(h, 13)   => :y
puts find_the_key(h, 999)  => :key_not_found

Is there a way to make this work by using pattern matching on the hash itself, ie, without converting into an array first?

Are there other ways of applying pattern matching to solve this problem?

Thank you

2

There are 2 answers

4
AudioBubble On

Here's the closest thing I can come up with. Start by limiting your pattern match to a_hash.values (which is really the only thing you're matching against anyway), assign the desired portion of the match to a variable using =>the_value, and then locate the key using a_hash.key(the_value):

h = {
  x: [1, 2, 3],
  y: [11, 12, 13],
  z: [100, 101],
}

def find_the_key(a_hash, a_number)
   case a_hash.values
   in [*, [*, ^a_number, *]=>the_value, *]
     the_key = a_hash.key(the_value)
   else
     :key_not_found
   end
 end
 

puts find_the_key(h, 13) #=>  y
puts find_the_key(h, 77) #=>  key_not_found
0
Kelvin On

This doesn't pattern match on the hash, but doesn't require a conversion either:

# Returns the first key whose value contains `value`; `nil` if not found
def find_the_key(h, value)
  h.detect { |k, v_arr| v_arr in [*, ^value, *] }&.[](0)
end

Keep in mind that pattern matching isn't actually needed; you can do this instead. But note that this may raise if the hash values aren't arrays.

def find_the_key(h, value)
  h.detect { |k, v_arr| v_arr.include?(value) }&.[](0)
end