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 variablearrau 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é :)