coreyward coreyward - 5 months ago 12
Ruby Question

Convert array-of-hashes to a hash-of-hashes, indexed by an attribute of the hashes

I've got an array of hashes representing objects as a response to an API call. I need to pull data from some of the hashes, and one particular key serves as an id for the hash object. I would like to convert the array into a hash with the keys as the ids, and the values as the original hash with that id.

Here's what I'm talking about:

api_response = [
{ :id => 1, :foo => 'bar' },
{ :id => 2, :foo => 'another bar' },
# ..

ideal_response = {
1 => { :id => 1, :foo => 'bar' },
2 => { :id => 2, :foo => 'another bar' },
# ..

There are two ways I could think of doing this.

  1. Map the data to the

  2. Use
    api_response.find { |x| x[:id] == i }
    for each record I need to access.

  3. A method I'm unaware of, possibly involving a way of using
    to build a hash, natively.

My method of mapping:

keys = { |x| x[:id] }
mapped = Hash[*]

I can't help but feel like there is a more performant, tidier way of doing this. Option 2 is very performant when there are a very minimal number of records that need to be accessed. Mapping excels here, but it starts to break down when there are a lot of records in the response. Thankfully, I don't expect there to be more than 50-100 records, so mapping is sufficient.

Is there a smarter, tidier, or more performant way of doing this in Ruby?


Ruby <= 2.0

Hash[ { |r| [r[:id], r] }]
# {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}} 

However, Hash::[] is pretty ugly and breaks the usual left-to-right OOP flow. That's why Facets proposed Enumerable#mash:

require 'facets'
api_response.mash { |r| [r[:id], r] }
# {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}} 

This basic abstraction (convert enumerables to hashes) was asked to be included in Ruby long ago, alas, without luck.

Ruby >= 2.1

[UPDATE] Still no love for Enumerable#mash, but now we have Array#to_h. Not ideal (because an intermediate array is created), but it's better than nothing:

# ruby 2.1 { |r| [r[:id], r] }.to_h