SupremeA SupremeA - 5 months ago 15
Ruby Question

Ruby select top 3 groups by amount

I have an array and I need to group the array by name and then sum up the total for each group and then select the top 3 with highest amount per groups.

Here is my sample array

@transactions =
[{"amount"=>-3000, "name"=>"CAR"},
{"amount"=>-600, "name"=>"BOAT"},
{"amount"=>-600, "name"=>"BOAT"},
{"amount"=>-600, "name"=>"BOAT"},
{"amount"=>-125, "name"=>"HOUSE" },
{"amount"=>-125, "name"=>"HOUSE" },
{"amount"=>-125, "name"=>"HOUSE" },
{"amount"=>-125, "name"=>"HOUSE" },
{"amount"=>-6000, "name"=>"PLANE" }]


The response should be:

[PLANE, CAR, BOAT]


I currently have this but it does not work:

transactions.group_by { |trans| trans.fetch('name') }.map do |amount, transactions|
[amount, transactions.map { |t| t[:amount] }.sum.abs.round(2)].select(3)

Answer

You can build a hash of the types (names), and sum the values as you go along with:

@transactions.each_with_object(Hash.new(0)) do |obj, hash|
  hash[obj["name"]] += obj["amount"].abs
end

Then you can add some moar magic to the end of that, or break it up into more lines (recommended for readability):

@transactions.each_with_object(Hash.new(0)) do |obj, hash|
  hash[obj["name"]] += obj["amount"].abs
end.sort_by(&:last).map(&:first).last(3).reverse

Basically, that's sorting by values (which turns your new hash to an array of tuples), then mapping the first value of each tuple (the name), then taking the top 3.

Edit

I didn't notice the negatives, so I summed while taking the absolute value of the amounts, then the sort_by sorts from smallest to largest, so take the last three and reverse to give you largest to smallest order.

It's a little complicated in a small block like that, I'd suggest breaking it up.