5

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?

4

2 回答 2

11

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}

Explanation:

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
于 2012-10-18T23:29:38.220 回答
5

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.

于 2012-10-19T00:22:24.017 回答