Группировать по уникальным значениям при суммировании / добавлении других значений - PullRequest
2 голосов
/ 12 апреля 2019

У меня есть структура данных, которая выглядит следующим образом:

arr = [
  {
    price: 2.0,
    unit: "meter",
    tariff_code: "4901.99",
    amount: 200
   },
   {
    price: 2.0,
    unit: "meter",
    tariff_code: "4901.99",
    amount: 200
   },
   {
    price: 14.0,
    unit: "yards",
    tariff_code: "6006.24",
    amount: 500
   },
   {
    price: 14.0,
    unit: "yards",
    tariff_code: "6006.24",
    amount: 500
  }
]

Мне нужно сгруппировать их по тарифному коду, суммируя цену и суммы, соответствующие этому тарифному коду. Так что мой ожидаемый результат должен быть:

[
  {
    price: 4.0,
    unit: "meter",
    tariff_code: "4901.99",
    amount: 400
   },
   {
    price: 2.0,
    unit: "yards",
    tariff_code: "6006.24",
    amount: 1000
   }
]

receipt_data[:order_items].group_by { |oi| oi[:tariff_code] }.values

Выражение group_by, использованное выше, позволит мне сгруппировать по коду тарифа, но я не могу найти способ суммировать другие значения. Я уверен, что есть хитрый однострочный способ сделать это ...

Ответы [ 4 ]

2 голосов
/ 13 апреля 2019

Просто чтобы добавить к веселью ответ, который использует group_by, как сказал @cary, и в основном копирует ответ Павла. Это очень плохая производительность и используется, только если массив маленький . Также он использует sum, который доступен только в Rails. (можно заменить на .map { |item| item[:price] }.reduce(:+) в чистом рубине)

arr.group_by { |a| a[:tariff_code] }.map do |tariff_code, items|
  {
    price: items.sum { |item| item[:price] },
    unit: items.first[:unit],
    tariff_code: tariff_code,
    amount: items.sum { |item| item[:amount] }
  }
end

Это было бы еще меньше, если бы это был массив объектов (возможно, объектов ActiveRecord) с методами вместо хешей.

arr.group_by(&:tariff_code).map do |tariff_code, items|
  {
    price: items.sum(&:price]),
    unit: items.first[:unit],
    tariff_code: tariff_code,
    amount: items.sum(&:amount)
  }
end
2 голосов
/ 12 апреля 2019

Более многословно:

grouped_items = arr.group_by { |oi| oi[:tariff_code] }
result = grouped_items.map do |tariff_code, code_items|
  price, amount = code_items.reduce([0, 0]) do |(price, amount), ci|
    [price + ci[:price], amount + ci[:amount]]
  end
  {
    price:       price,
    unit:        code_items.first[:unit],
    tariff_code: tariff_code,
    amount:      amount
  }
end
#[
#  {:price=>4.0, :unit=>"meter", :tariff_code=>"4901.99", :amount=>400}
#  {:price=>28.0, :unit=>"yards", :tariff_code=>"6006.24", :amount=>1000}
#]
2 голосов
/ 12 апреля 2019

Существует два стандартных способа решения проблем такого рода.Один из них, который я выбрал, заключается в использовании формы Hash # update (он же merge!), которая использует блок для определения значений ключей, которые присутствуют в обоих объединяемых хэшах.Другой способ - использовать Enumerable # group_by , который, как я ожидаю, вскоре будет использован другим в другом ответе.Я не верю, что какой-либо подход предпочтителен с точки зрения эффективности или читабельности.

arr.each_with_object({}) do |g,h|
  h.update(g[:tariff_code]=>g) do |_,o,n|
    { price: o[:price]+n[:price], unit: o[:unit], amount: o[:amount]+n[:amount] }
  end
end.values
  #=> [{:price=>4.0,  :unit=>"meter", :amount=>400},
  #    {:price=>28.0, :unit=>"yards", :amount=>1000}] 

Обратите внимание, что получатель values выглядит так:

{"4901.99"=>{:price=>4.0,  :unit=>"meter", :amount=>400},
{"6006.24"=>{:price=>28.0, :unit=>"yards", :amount=>1000}} 
1 голос
/ 13 апреля 2019

Простой подход, но легко добавить новые ключи для суммирования и изменить групповой ключ.Не уверен насчет эффективности, но в 500_000 раз отметка arr.map здесь выглядит хорошо

#<Benchmark::Tms:0x00007fad0911b418 @label="", @real=1.480799000000843, @cstime=0.0, @cutime=0.0, @stime=0.0017340000000000133, @utime=1.4783359999999999, @total=1.48007>

summ_keys = %i[price amount]
grouping_key = :tariff_code
result = Hash.new { |h, k| h[k] = {} }
arr.map do |h|
  cumulative = result[h[grouping_key]]
  h.each do |k, v|
    case k
    when *summ_keys
      cumulative[k] = (cumulative[k] || 0) + h[k]
    else
      cumulative[k] = v
    end
  end
end
p result.values

# [{:price=>4.0, :unit=>"meter", :tariff_code=>"4901.99", :amount=>400},
#  {:price=>28.0, :unit=>"yards", :tariff_code=>"6006.24", :amount=>1000}]
...