Convert Array-Of-Hashes to a Hash-Of-Hashes, Indexed by an Attribute of the Hashes

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

Ruby <= 2.0

> Hash[api_response.map { |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.

Note that your use case is covered by Active Support: Enumerable#index_by

Ruby >= 2.1

[UPDATE] Still no love for Enumerable#mash, but now we have Array#to_h. It creates an intermediate array, but it's better than nothing:

> object = api_response.map { |r| [r[:id], r] }.to_h

Convert Array to Hash while preserving Array index values in Ruby

Using Enumerable#each_with_index:

Hash[array.each_with_index.map { |value, index| [index, value] }]
# => {0=>"Adult", 1=>"Family", 2=>"Single", 3=>"Child"}

As @hirolau commented, each_with_index.map can also be written as map.with_index.

Hash[array.map.with_index { |value, index| [index, value] }]
# => {0=>"Adult", 1=>"Family", 2=>"Single", 3=>"Child"}

UPDATE

Alterantive that use Hash#invert:

Hash[array.map.with_index{|*x|x}].invert
# => {0=>"Adult", 1=>"Family", 2=>"Single", 3=>"Child"}
Hash[[*array.map.with_index]].invert
# => {0=>"Adult", 1=>"Family", 2=>"Single", 3=>"Child"}

Ruby -- convert array to hash with specific index

Try the below :

a = [
{
'date' => '2013-09-01',
'impressions' => 50,
'clicks' => 20,
},
{
'date' => '2013-09-02',
'impressions' => 51,
'clicks' => 22,
},
]

Hash[a.map{|h| [h['date'],Hash[h.to_a[1..-1]]] }]

or

Hash[a.map{|h| [h['date'],h.select{|k,_| k != 'date'}] }]

# => {"2013-09-01"=>{"impressions"=>50, "clicks"=>20},
#"2013-09-02"=>{"impressions"=>51, "clicks"=>22}}

What is the best way to convert an array to a hash in Ruby

NOTE: For a concise and efficient solution, please see Marc-André Lafortune's answer below.

This answer was originally offered as an alternative to approaches using flatten, which were the most highly upvoted at the time of writing. I should have clarified that I didn't intend to present this example as a best practice or an efficient approach. Original answer follows.


Warning! Solutions using flatten will not preserve Array keys or values!

Building on @John Topley's popular answer, let's try:

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

This throws an error:

ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10

The constructor was expecting an Array of even length (e.g. ['k1','v1,'k2','v2']). What's worse is that a different Array which flattened to an even length would just silently give us a Hash with incorrect values.

If you want to use Array keys or values, you can use map:

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

This preserves the Array key:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

Convert array of 2-element arrays into a hash, where duplicate keys append additional values

Using functional baby steps:

irb:01.0> array = [[:a,:b],[:a,:c],[:c,:b]]
#=> [[:a, :b], [:a, :c], [:c, :b]]

irb:02.0> array.group_by(&:first)
#=> {:a=>[[:a, :b], [:a, :c]], :c=>[[:c, :b]]}

irb:03.0> array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] }
#=> [[:a, [:b, :c]], [:c, [:b]]]

irb:04.0> Hash[ array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } ]
#=> {:a=>[:b, :c], :c=>[:b]}

Using imperative style programming:

irb:10.0> h = Hash.new{ |h,k| h[k]=[] }
#=> {}

irb:11.0> array.each{ |k,v| h[k] << v }
#=> [[:a, :b], [:a, :c], [:c, :b]]

irb:12.0> h
#=> {:a=>[:b, :c], :c=>[:b]}

As an imperative one-liner:

irb:13.0> h = Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
#=> {:a=>[:b, :c], :c=>[:b]}

Or using everyone's favorite inject:

irb:14.0> array.inject(Hash.new{ |h,k| h[k]=[] }){ |h,(k,v)| h[k] << v; h }
#=> {:a=>[:b, :c], :c=>[:b]}

If you really want to have single values not collided as an array, you can either un-array them as a post-processing step, or use a different hash accumulation strategy that only creates an array upon collision. Alternatively, wrap your head around this:

irb:17.0> hashes = array.map{ |pair| Hash[*pair] } # merge many mini hashes
#=> [{:a=>:b}, {:a=>:c}, {:c=>:b}]

irb:18.0> hashes.inject{ |h1,h2| h1.merge(h2){ |*a| a[1,2] } }
#=> {:a=>[:b, :c], :c=>:b}

Using #select on either a hash or an array of hashes

You could do

[my_hash[:test]].flatten.select { |s| s[:another_test] == 123 }

How to convert an array to a hash using Mongoid Pipeline?

Use $map to map your array from ['v1', 'v2', 'v3'] to [['v1', true], ['v2', true], ['v3', true]], then use https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/ to convert that to a hash.

Rails convert computed array of hashes into a nested array

It's actually simple:

records.map { |h| [h['created_at'], h['delta']] }
=> [["2017-02-26 18:50:20.654996", nil],
["2017-02-27 18:50:20.654996", 2000],
["2017-02-28 18:50:20.654996", 3000]]

In Ruby, you can access hash value by passing key name in brackets, like in this instance.

Or you can use Hash#values_at:

records.map { |h| h.values_at('created_at', 'delta') }

and get the same result.



Related Topics



Leave a reply



Submit