简体   繁体   中英

Ruby aggregate selective values within a collection of hashes

I have an array of hashes with the keys being countries and the values being number of days.

I would like to aggregate over the hashes and sum the values for the countries that are the same.

the array could look like this countries = [{"Country"=>"Brazil", "Duration"=>731/1 days}, {"Country"=>"Brazil", "Duration"=>365/1 days}]

I would like this to return something on the lines of: [{"Country" => "Brazil", "Duration"=>1096/1 days}]

I tried the other questions on SO like this one

countries.inject{|new_h, old_h| new_h.merge(old_h) {|_, old_v, new_v| old_v + new_v}}

Produces {"Country"=>"BrazilBrazil", "Duration"=>1096/1 days}

Is there a way to selectively only merge specific values?

This uses the form of Hash::new that creates a creates an empty hash with a default value (here 0 ). For a hash h created that way, h[k] returns the default value if the hash does not have a key k . The hash is not modified.

countries = [{"Country"=>"Brazil",    "Duration"=>"731/1 days"},
             {"Country"=>"Argentina", "Duration"=>"123/1 days"},
             {"Country"=>"Brazil",    "Duration"=>"240/1 days"},
             {"Country"=>"Argentina", "Duration"=>"260/1 days"}]

countries.each_with_object(Hash.new(0)) {|g,h| h[g["Country"]] += g["Duration"].to_i }.
  map { |k,v| { "Country"=>k, "Duration"=>"#{v}/1 days" } }
    #=> [{"Country"=>"Brazil",    "Duration"=>"971/1 days"},
    #    {"Country"=>"Argentina", "Duration"=>"383/1 days"}]

The first hash passed to the block and assigned to the block variable g .

g = {"Country"=>"Brazil", "Duration"=>"731/1 days"}

At this time h #=> {} . We then compute

h[g["Country"]] += g["Duration"].to_i
  #=> h["Brazil"] += "971/1 days".to_i
  #=> h["Brazil"] = h["Brazil"] + 971
  #=> h["Brazil"] = 0 + 971 # h["Brazil"]

See String#to_i for an explanation of why "971/1 days".to_i returns 971 .

h["Brazil"] on the right of the equality returns the default value of 0 because h does not (yet) have a key "Brazil" . Note that h["Brazil"] on the right is syntactic sugar for h.[]("Brazil") , whereas on the left it is syntactic sugar for h.[]=(h["Brazil"] + 97) . It is Hash#[] that returns the default value when the hash does not have the given key. The remaining steps are similar.

You may update your code as follows:

countries.inject do |new_h, old_h| 
    new_h.merge(old_h) do |k, old_v, new_v|
        if k=="Country" then old_v else old_v + new_v end
    end 
end
#  => {"Country"=>"Brazil", "Duration"=>1096} 

where you basically use the k (for key ) argument to switch among different merging policies.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM