How to find a hash key containing a matching value
Asked Answered
L

11

242

Given I have the below clients hash, is there a quick ruby way (without having to write a multi-line script) to obtain the key given I want to match the client_id? E.g. How to get the key for client_id == "2180"?

clients = {
  "yellow"=>{"client_id"=>"2178"}, 
  "orange"=>{"client_id"=>"2180"}, 
  "red"=>{"client_id"=>"2179"}, 
  "blue"=>{"client_id"=>"2181"}
}
Leveret answered 25/9, 2010 at 13:38 Comment(0)
L
196

You could use Enumerable#select:

clients.select{|key, hash| hash["client_id"] == "2180" }
#=> [["orange", {"client_id"=>"2180"}]]

Note that the result will be an array of all the matching values, where each is an array of the key and value.

Lindley answered 25/9, 2010 at 13:43 Comment(5)
@Leveret The difference between find and select is that find returns the first match and select (which is aliased by findAll) returns all matches.Lindley
I see, so this would be the safer option for instances where there is more than one match.Leveret
This is better than creating a whole new hash (by calling invert) just to find an item.Valuer
Note that as of Ruby 1.9.3, this will return a new hash with the matches. It will not return an array, as it used to in Ruby <= 1.8.7. clients.select{|key, hash| hash["client_id"] == "2180" } # => {"orange"=>{"client_id"=>"2180"}}Prink
To get the key(s), simply put clients.select{|key, hash| hash["client_id"] == "2180" }.keysAntispasmodic
C
480

Ruby 1.9 and greater:

hash.key(value) => key

Ruby 1.8:

You could use hash.index

hsh.index(value) => key

Returns the key for a given value. If not found, returns nil.

h = { "a" => 100, "b" => 200 }
h.index(200) #=> "b"
h.index(999) #=> nil

So to get "orange", you could just use:

clients.key({"client_id" => "2180"})
Calculate answered 25/9, 2010 at 13:43 Comment(3)
This would get kind of messy if the hashes had multiple keys, because you'd need to give the entire hash to index.Lindley
Hash#index is renamed to Hash#key in Ruby 1.9Onomastics
Note that this only returns the first match, if there are multiple hash pairings with the same value, it'll return the first key with a matching value.Adumbral
L
196

You could use Enumerable#select:

clients.select{|key, hash| hash["client_id"] == "2180" }
#=> [["orange", {"client_id"=>"2180"}]]

Note that the result will be an array of all the matching values, where each is an array of the key and value.

Lindley answered 25/9, 2010 at 13:43 Comment(5)
@Leveret The difference between find and select is that find returns the first match and select (which is aliased by findAll) returns all matches.Lindley
I see, so this would be the safer option for instances where there is more than one match.Leveret
This is better than creating a whole new hash (by calling invert) just to find an item.Valuer
Note that as of Ruby 1.9.3, this will return a new hash with the matches. It will not return an array, as it used to in Ruby <= 1.8.7. clients.select{|key, hash| hash["client_id"] == "2180" } # => {"orange"=>{"client_id"=>"2180"}}Prink
To get the key(s), simply put clients.select{|key, hash| hash["client_id"] == "2180" }.keysAntispasmodic
M
50

You can invert the hash. clients.invert["client_id"=>"2180"] returns "orange"

Monecious answered 25/9, 2010 at 13:45 Comment(2)
This also seems like a clever way (because it's short) to do it!Leveret
this is what i need for form arrays (for select boxes) which create a backwards hashMelitta
E
24

You could use hashname.key(valuename)

Or, an inversion may be in order. new_hash = hashname.invert will give you a new_hash that lets you do things more traditionally.

Educatory answered 14/6, 2012 at 0:54 Comment(2)
This is the proper way to do it in recent versions (1.9+) of Ruby.Hofmannsthal
#invert is a really bad idea in this case, since you are essentially allocating memory for throw-away hash object just for the sake of finding a key. Depending on hash size it have serious performance impactSpoilage
T
20

try this:

clients.find{|key,value| value["client_id"] == "2178"}.first
Titanate answered 25/9, 2010 at 13:43 Comment(3)
This will throw an exception if the find returns nil, because you can't call .first on nil.Liddie
If using Rails you can use .try(:first) instead of .first to avoid exceptions (If you are expecting it to be possible for the value to be missing).Commentator
in Ruby 2.3.0 + you can use safe navigator &.first at the end of block to prevent from Nil ExceptionWald
W
14

According to ruby doc http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-key key(value) is the method to find the key on the base of value.

ROLE = {"customer" => 1, "designer" => 2, "admin" => 100}
ROLE.key(2)

it will return the "designer".

Weinert answered 2/5, 2013 at 8:19 Comment(0)
M
6

From the docs:

  • (Object?) detect(ifnone = nil) {|obj| ... }
  • (Object?) find(ifnone = nil) {|obj| ... }
  • (Object) detect(ifnone = nil)
  • (Object) find(ifnone = nil)

Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.

If no block is given, an enumerator is returned instead.

(1..10).detect  {|i| i % 5 == 0 and i % 7 == 0 }   #=> nil
(1..100).detect {|i| i % 5 == 0 and i % 7 == 0 }   #=> 35

This worked for me:

clients.detect{|client| client.last['client_id'] == '2180' } #=> ["orange", {"client_id"=>"2180"}] 

clients.detect{|client| client.last['client_id'] == '999999' } #=> nil 

See: http://rubydoc.info/stdlib/core/1.9.2/Enumerable#find-instance_method

Microdot answered 25/4, 2013 at 3:27 Comment(0)
W
4

The best way to find the key for a particular value is to use key method that is available for a hash....

gender = {"MALE" => 1, "FEMALE" => 2}
gender.key(1) #=> MALE

I hope it solves your problem...

Wilbanks answered 29/4, 2013 at 12:31 Comment(0)
K
2

Another approach I would try is by using #map

clients.map{ |key, _| key if clients[key] == {"client_id"=>"2180"} }.compact 
#=> ["orange"]

This will return all occurences of given value. The underscore means that we don't need key's value to be carried around so that way it's not being assigned to a variable. The array will contain nils if the values doesn't match - that's why I put #compact at the end.

Kailyard answered 13/3, 2014 at 16:46 Comment(0)
T
1

Heres an easy way to do find the keys of a given value:

    clients = {
      "yellow"=>{"client_id"=>"2178"}, 
      "orange"=>{"client_id"=>"2180"}, 
      "red"=>{"client_id"=>"2179"}, 
      "blue"=>{"client_id"=>"2181"}
    }

    p clients.rassoc("client_id"=>"2180")

...and to find the value of a given key:

    p clients.assoc("orange") 

it will give you the key-value pair.

Trowbridge answered 8/8, 2014 at 21:1 Comment(0)
C
0

We write 2023: (so for all stumbling in here)

find is what you are searching for.

But find returns an array on match or NIL for no match. If we use the argument of find (a proc that is called if no match), and use proc {[]}, we get an empty array on a non-matching find, that fits better to a hash.

people = { 
    ralph: { name: "rafael", … },
    eve: { name: "eveline", … }
    …
    }
    
people[:eve] => {name: "eveline",…} 
people.find { |nick, person| person.name=="rafael" }[1] => { name: "rafael", … }

and

people[:tosca] => nil
people.find { |nick, person| person.name=="toska" }[1] => BANG ([] on nil) 

but

people.find(proc {[]}) { |nick, person| person.name=="toska" }[1] => nil

So if you have an id-like attribute, you can do like that:

person=people[id]
person||=people.find({[]}) { |p| p.nick == id }[1]
person||=people.find({[]}) { |p| p.other_nick == id }[1]

raise error unless person

    
            



    
    
Centesimal answered 27/11, 2023 at 5:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.