disastrous-charly
disastrous-charly

Reputation: 1085

Ruby - array of hashes group by based on similar hash key

I have an array of hashes who looks like :

[{"PROJECT"=>"awesome_project1","VERSION"=>128, "STATUS"=>"not required"},
 {"PROJECT"=>"awesome_project2", "VERSION"=>32, "STATUS"=>"finished"},
 {"PROJECT"=>"awesome_project1", "VERSION"=>64, "STATUS"=>"scheduled"}]

And I'm trying to merge the hashes based on one of the hask key, knowing that the key to group_by can change. So if I want to group_by "PROJECT" it would looks like :

[{"PROJECT"=>"awesome_project1","VERSION"=>[128,64], "STATUS"=>["not required","scheduled"]},
 {"PROJECT"=>"awesome_project2", "VERSION"=>32, "STATUS"=>"finished"}]

For the moment the closest I am to the result is by using group_by.

Do you have an idea on how to do it ?

Upvotes: 1

Views: 64

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110675

You can always solve problems of this kind in one of two ways. The first is to use Enumerable#group_by, as @Aleksei (aka mudsie) has done. The other way is to create a hash whose keys are the values over which the aggregation is being done (here the values of "PROJEXT"), then at the end extract the values of that hash. That's the approach I've taken below.

Code

def doit(arr)
  arr.each_with_object({}) do |g,h|
    h.update(g["PROJECT"]=>(g.merge("STATUS"=>[g["STATUS"]]))) do |_,o1,n1|
      o1.merge(n1) { |k2,o2,n2| k2=="STATUS" ? (o2+n2) : o2 }  
    end
  end.values
end

Example

arr = [
  {"PROJECT"=>"awesome_project1", "VERSION"=>128, "STATUS"=>"not required"},
  {"PROJECT"=>"awesome_project2", "VERSION"=> 32, "STATUS"=>"finished"},
  {"PROJECT"=>"awesome_project1", "VERSION"=> 64, "STATUS"=>"scheduled"}
]

doit arr
  #=> [{"PROJECT"=>"awesome_project1", "VERSION"=>128,
  #       "STATUS"=>["not required", "scheduled"]},
  #    {"PROJECT"=>"awesome_project2", "VERSION"=>32,
  #       "STATUS"=>["finished"]}] 

This is not quite the return value you asked for. In the second hash you wanted "STATUS"=>"finished", not "STATUS"=>["finished"]. I intentionally made the value an array, however, as it nearly always saves trouble down-the-line by making all values instances of the same class (here arrays). If it must be "STATUS"=>"finished", let me know and I will edit my answer.

Explanation

...under construction

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

You need to group_by and then reduce values merging them.

input.
  group_by { |h| h["PROJECT"] }.
  values.
  map do |v|
    v.reduce({}) do |acc, h|
      acc.merge(h) { |_, v1, v2| v1 == v2 ? v1 : [*v1, *v2] }
    end
  end

#⇒ [{"PROJECT"=>"awesome_project1",
#    "VERSION"=>[128, 64],
#    "STATUS"=>["not required", "scheduled"]},
#   {"PROJECT"=>"awesome_project2",
#    "VERSION"=>32,
#    "STATUS"=>"finished"}]

Upvotes: 2

Related Questions