I have the following array:
myarray = [
['usa','primary', 'john'],
['france','primary', 'lira'],
['usa', 'secondary', 'steve'],
['germany', 'primary', 'jeff'],
['france', 'secondary', 'ben']
]
I want to convert it to an array of hash like:
[
{:country => 'usa', :primary => 'john', :secondary => 'steve'},
{:country => 'france', :primary => 'lira', :secondary => 'ben'},
{:country => 'germany', :primary => 'jeff', :secondary => ''}
]
I can do it by looping through the array and putting the values into a hash for "primary". How can I add "secondary" to the existing hash that may already be inserted into the array?
The to_h method is defined in the array class. It works to convert an array to a hash in the form of key-value pairs. The method converts each nested array into key-value pairs. The method also accepts a block.
In Ruby you can create a Hash by assigning a key to a value with => , separate these key/value pairs with commas, and enclose the whole thing with curly braces.
Ruby | Array to_h() function Array#to_h() : to_h() is a Array class method which returns the result of interpreting ary as an array of [key, value] pairs. Return: the result of interpreting ary as an array of [key, value] pairs.
Array#select() : select() is a Array class method which returns a new array containing all elements of array for which the given block returns a true value. Return: A new array containing all elements of array for which the given block returns a true value.
With a bit of Higher order programming:
myarray.reduce({}) do |accu, (country, func, name)|
accu[country] ||= {}
accu[country][func.to_sym] = name
accu
end.map{|k, h| h[:country] = k; h}
Reduce will take an accumulator, in this case we start with an empty hash and go through the array. We match the triples in the array to the variables country, func, name
. Since we want to group things by country we create that as our first Hash key. It should contain a hash so we make sure that is corresponds to an array with accu[country] ||= {}
.
Then we add our key value pair converting func
to a symbol. Finally we return our modified accumulator that will be passed to the next iteration.
This will return a data structure like this:
{"usa"=>{:primary=>"john", :secondary=>"steve"},
"france"=>{:primary=>"lira", :secondary=>"ben"},
"germany"=>{:primary=>"jeff"}}
Now we need to transform it into a array of hashes rather than a big hash. We do this by calling map
on it and in the process we add country
as a key to the hash.
Now one thing that the algorithm above doesn't do is to check for missing values, resp. it doesn't guarantee that both :primary
and :secondary
are present. You can do that by modifying the map
to this:
.map do |k, h|
h[:country] = k
h[:primary] ||= ""
h[:secondary] ||= ""
h
end
Here's a fun, albeit confusing, way to do it.
format = Hash.new{ |h,k| h[k] = {:country => k, :primary => '', :secondary => ''} }
myarray.inject(format){ |result, (c,k,v)| result[c][k.to_sym] = v; result }.values
# => [
{:country=>"usa", :primary=>"john", :secondary=>"steve"},
{:country=>"france", :primary=>"lira", :secondary=>"ben"},
{:country=>"germany", :primary=>"jeff", :secondary=>""} ]
Basically the format
hash defines the way you want to generate your output. The inject
call collects the results by country. The values
call just grabs the actual results without the country hash keys.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With