245

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"}
}

11 Answers 11

487

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"})
Sign up to request clarification or add additional context in comments.

4 Comments

This would get kind of messy if the hashes had multiple keys, because you'd need to give the entire hash to index.
Hash#index is renamed to Hash#key in Ruby 1.9
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.
This is called the perfect answer!!!
197

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.

5 Comments

@Coderama The difference between find and select is that find returns the first match and select (which is aliased by findAll) returns all matches.
I see, so this would be the safer option for instances where there is more than one match.
This is better than creating a whole new hash (by calling invert) just to find an item.
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"}}
To get the key(s), simply put clients.select{|key, hash| hash["client_id"] == "2180" }.keys
52

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

2 Comments

This also seems like a clever way (because it's short) to do it!
this is what i need for form arrays (for select boxes) which create a backwards hash
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.

2 Comments

This is the proper way to do it in recent versions (1.9+) of Ruby.
#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 impact
20

try this:

clients.find{|key,value| value["client_id"] == "2178"}.first

3 Comments

This will throw an exception if the find returns nil, because you can't call .first on nil.
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).
in Ruby 2.3.0 + you can use safe navigator &.first at the end of block to prevent from Nil Exception
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".

Comments

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

Comments

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

Comments

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.

Comments

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.

Comments

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

    
            



    
    

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.