0

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

Zack Xu
  • 11,505
  • 9
  • 70
  • 78
  • You should include the desired output and output format in your question. What if hash has multiple keys that include the same values? –  Jan 07 '23 at 21:43
  • If multiple keys' values include the same number, then return the first key that matches, as my code above does. – Zack Xu Jan 07 '23 at 21:55

2 Answers2

1

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
  • Thanks for coming up with this solution. Nice way to capture a part of the pattern matched. – Zack Xu Jan 08 '23 at 10:39
  • 1
    If he answers his question, please consider accepting it – Rajagopalan Jan 08 '23 at 23:22
  • However, `a_hash.values` is actually an array. I was wondering if pattern matching on the hash itself was possible. – Zack Xu Jan 10 '23 at 00:02
  • I do not believe it’s possible to match unknown keys since a hash is for all intents and purposes inherently unordered, and because the key IS the access point. I don’t believe there is any way to match against a key aside from conversion to enumerable and looping or conversion to string. I think you’re simply attempting to shoehorn pattern matching into the wrong job. –  Jan 10 '23 at 00:24
0

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
Kelvin
  • 20,119
  • 3
  • 60
  • 68