J'ai une structure de données qui ressemble à ceci:
[ { price: 4.0, unit: "meter", tariff_code: "4901.99", amount: 400 }, { price: 2.0, unit: "yards", tariff_code: "6006.24", amount: 1000 } ]
Je dois regrouper tous ces éléments par code_ tarifaire, tout en additionnant le prix et les montants correspondant à ce code tarifaire. Donc, ma sortie attendue devrait être:
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 } ]
reception_data [: order_items] .group_by {| oi | oi [: code_ tarif]} .values
L'instruction group_by
utilisée ci-dessus me permettra de grouper par code_ tarifaire mais je ne parviens pas à trouver un moyen de faire la somme les autres valeurs. Je suis sûr qu'il existe un moyen simple d'accomplir cela ...
4 Réponses :
Il existe deux méthodes standard pour résoudre les problèmes de ce type. L'une, que j'ai prise, consiste à utiliser la forme Hash # update (aka Notez que le récepteur des merge!
) qui utilise un bloc pour déterminer les valeurs des clés présentes dans les deux hachages en cours de fusion. L'autre méthode consiste à utiliser Enumerable # group_by a >, que je pense que quelqu'un emploiera bientôt dans une autre réponse. Je ne pense pas qu'une approche soit préférable en termes d'efficacité ou de lisibilité. {"4901.99"=>{:price=>4.0, :unit=>"meter", :amount=>400},
{"6006.24"=>{:price=>28.0, :unit=>"yards", :amount=>1000}}
valeurs
est considéré comme: 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}]
Plus verbeux:
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} #]
Une approche simple, mais il est facile d'ajouter de nouvelles clés pour la sommation et de changer une clé de groupe. Je ne suis pas sûr de l'efficacité, mais 500_000 fois le benchmark de arr.map
semble bon ici
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}]
#<Benchmark::Tms:0x00007fad0911b418 @label="", @real=1.480799000000843, @cstime=0.0, @cutime=0.0, @stime=0.0017340000000000133, @utime=1.4783359999999999, @total=1.48007>
Juste pour ajouter à l'amusement, la réponse qui utilise group_by
comme l'a dit @cary, et copie principalement la réponse de Pavel. Ceci est très mauvais en termes de performances et à n'utiliser que si le tableau est petit . Il utilise également sum
qui n'est disponible que dans Rails. (peut être remplacé par .map {| item | item [: price]} .reduce (: +)
en pur rubis)
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
Cela aurait été pair plus petit s'il s'agissait d'un tableau d'objets (peut-être des objets ActiveRecord) avec des méthodes au lieu de hachages.
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
Agréable et propre!
Il me semble que vous devez utiliser un itérateur comme
select
, qui filtre sur des valeurs uniques detarrif_code
. Si vous essayez avec chaque élément d'un tableau, les méthodes Array sont ce que vous pouvez effectuer sur ce tableau. ruby-doc.org/core-2.4.1/Array.htmlJ'ai modifié votre tableau car il contenait quelques petites erreurs (
montant:
était: montant:
à deux endroits). J'ai également attribué une variablearr
au tableau. Cela permet aux lecteurs de se référer à la variable dans les réponses et les commentaires sans avoir à la définir.Merci pour ça! Désolé, j'étais pressé :)