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?

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

4 Comments

Thanks. But here you put country name outside e.g. "usa"=>{:primary=>"john", :secondary=>"steve"} while I wish to have {:country => 'usa', :primary => 'john', :secondary => 'steve'}
Try running the code, it does exactly that. What I talk about in the answer is the intermediate step between reduce and map. Please read carefully.
you rock man :) but let me ask you, what if we have more than one identity to group by. For example, myarray = [ ['usa', 'california', 90072, 'primary', 'john'], ['usa', 'california', 90072, 'secondary', 'paul'], ['france', 'brittany', 73829, primary', 'lira'] that means here if I have grouping with more than one column, not only country. Group by country, state and zipcode :)
You can use an array as a hash key, so instead of having simply accu[country] you could have a key made out of an array of all those values.
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.

1 Comment

+1 I was wondering whether someone would use the Hash.new thing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.