Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group array of hashes by multiple keys

I would like to group_by multiple keys: orders, idx, account, etc. The code below is a modified version of Ruby on Rails - Hash of Arrays, group by and sum by column name. Can anyone recommend a way to group on multiple keys and sum multiple values?

For example, in the below code, I group only on "orders". I would like to group on orders, idx, and account.

group_hashes some_array, ["order","idx","account"] ["money","amt"]

vs.

group_hashes some_array, "order", "money","amt" 

Code:

some_array=[{"idx"=>"1234", "account"=>"abde", "amt"=>"2", "money"=>"4.00", 
      "order"=>"00001"}, {"idx"=>"1235", "account"=>"abde",  "amt"=>"2" ,
      "money"=>"2.00", "order"=>"00001"}, {"idx"=>"1235", 
      "account"=>"abde", "amt"=>"2" , "money"=>"3.00", "order"=>"00002"}]  # => [{"idx"=>"1234", "account"=>"abde", "amt"=>"2", "money"=>"4.00", "order"=>"00001"}, {"idx"=>"1235", "account"=>"abde", "amt"=>"2", "money"=>"2.00", "order"=>"00001"}, {"idx"=>"1235", "account"...

#group_hashes arr, "order", "money"
def group_hashes arr, group_field, *sum_field
  arr.inject({}) do |res, h|
    (res[h[group_field]] ||= {}).merge!(h) do |key, oldval, newval|
        sum_field.include?(key) ? (oldval.to_f + newval.to_f).to_s : oldval  # => "1234", "abde", "4.0", "6.0", "00001"
    end                                                                      # => {"idx"=>"1234", "account"=>"abde", "amt"=>"2", "money"=>"4.00", "order"=>"00001"}, {"idx"=>"1234", "account"=>"abde", "amt"=>"4.0", "money"=>"6.0", "order"=>"00001"}, {"idx"=>"1235", "account"...
    res                                                                      # => {"00001"=>{"idx"=>"1234", "account"=>"abde", "amt"=>"2", "money"=>"4.00", "order"=>"00001"}}, {"00001"=>{"idx"=>"1234", "account"=>"abde", "amt"=>"4.0", "money"=>"6.0", "order"=>"00001"}}, {"0...
  end.values                                                                 # => [{"idx"=>"1234", "account"=>"abde", "amt"=>"4.0", "money"=>"6.0", "order"=>"00001"}, {"idx"=>"1235", "account"=>"abde", "amt"=>"2", "money"=>"3.00", "order"=>"00002"}]
end                                                                          # => nil

group_hashes some_array, "order", "money","amt"  # => [{"idx"=>"1234", "account"=>"abde", "amt"=>"4.0", "money"=>"6.0", "order"=>"00001"}, {"idx"=>"1235", "account"=>"abde", "amt"=>"2", "money"=>"3.00", "order"=>"00002"}]

Expected output from input:

input =[  {"idx"=>"1234", "account"=>"abde", "amt"=>"2", "money"=>"4.00", "order"=>"00001"}, 
          {"idx"=>"1234", "account"=>"abde",  "amt"=>"2" ,"money"=>"2.00", "order"=>"00001"}, 
          {"idx"=>"1235", "account"=>"abde", "amt"=>"2" , "money"=>"3.00", "order"=>"00002"},
          {"idx"=>"1235", "account"=>"abde", "amt"=>"3", "money"=>"4.00", "order"=>"00002"},
          {"idx"=>"1234", "account"=>"ddd", "amt"=>"2", "money"=>"4.00", "order"=>"00003"},
          {"idx"=>"1234", "account"=>"ddd", "amt"=>"2", "money"=>"4.00", "order"=>"00003"}
        ]
output =[  {"idx"=>"1234", "account"=>"abde", "amt"=>"4", "money"=>"6.00", "order"=>"00001"}, 
          {"idx"=>"1235", "account"=>"abde", "amt"=>"5" , "money"=>"7.00", "order"=>"00002"},
          {"idx"=>"1234", "account"=>"ddd", "amt"=>"4", "money"=>"8.00", "order"=>"00003"},
        ] 
like image 680
user2012677 Avatar asked Jun 23 '13 18:06

user2012677


People also ask

How do you create an array of hashes?

Creating an array of hashes You are allowed to create an array of hashes either by simply initializing array with hashes or by using array. push() to push hashes inside the array. Note: Both “Key” and :Key acts as a key in a hash in ruby.

Can an array be a Hash key?

All lists, mutable or not, hash by value. And you can use e.g. mutable ArrayList as hash keys.

Can a Hash key have multiple values?

Each key can only have one value. But the same value can occur more than once inside a Hash, while each key can occur only once.

How to sort array of objects in Ruby?

The Ruby sort method works by comparing elements of a collection using their <=> operator (more about that in a second), using the quicksort algorithm. You can also pass it an optional block if you want to do some custom sorting. The block receives two parameters for you to specify how they should be compared.


1 Answers

I would do it this way:

def group_hashes arr, group_fields
  arr.group_by {|hash| hash.values_at(*group_fields).join ":" }.values.map do |grouped|
    grouped.inject do |merged, n|
      merged.merge(n) do |key, v1, v2|
        group_fields.include?(key) ? v1 : (v1.to_f + v2.to_f).to_s
      end
    end 
  end
end

You can group by multiple keys by joining them with some delimiter, see arr.group_by {|hash| hash.values_at(*group_fields).join ":" }

And I suppose, you don't need sum_field, because generally all fields that aren't grouped should be summed up.

like image 51
Yuri Golobokov Avatar answered Oct 16 '22 20:10

Yuri Golobokov