如何在 Ruby 中合并具有相同键的哈希数组

How to merge array of hashes with same keys in Ruby

提问人:jkvithanage 提问时间:4/16/2023 最后编辑:jkvithanage 更新时间:4/17/2023 访问量:134

问:

我有一组这样的项目:

items = [{name: 'item_1', qty: 1, unit_price: 5},
{name: 'item_1', qty: 2, unit_price: 5},
{name: 'item_2', qty: 3, unit_price: 10},
{name: 'item_2', qty: 2, unit_price: 10}]

我想通过对数量求和,将同名的项目合并为一个哈希值。上述示例的预期输出应为:

[{name: 'item_1', qty: 3, unit_price: 5},
{name: 'item_2', qty: 5, unit_price: 10}]
数组 Ruby-on-Rails Ruby 哈希表

评论

1赞 mechnicov 4/16/2023
为什么产出中 item2 的单价不同?
1赞 max 4/16/2023
欢迎来到 Stackoverflow。这个问题被否决的原因是你没有表现出任何解决问题的尝试。要求其他人完成一项任务,就好像他们一开始就被分配了任务一样,这被认为是不礼貌的,社区通常不喜欢“Gimme dah codez”的问题。idownvotedbecau.se/noattempt
0赞 jkvithanage 4/16/2023
@mechnicov这些都不是实际数据。我刚刚写了一个与实际示例相似的示例。无论如何,我解决了这个问题。
0赞 jkvithanage 4/16/2023
@max,谢谢你的建议。老实说,我在尝试了几个小时后将此作为问题发布。无论如何,我会在下一个问题中发布我的一些尝试。
1赞 max 4/16/2023
另外,您确定同名的单价始终相同吗?假设你有.你不能把它总结为.{name: 'item_2', qty: 3, unit_price: 7 }, {name: 'item_2', qty: 2, unit_price: 10}{name: 'item_2', qty: 5, unit_price: 10}

答:

1赞 marmeladze 4/16/2023 #1

您可以从这里开始,找到最佳解决方案

items.inject([]) do |acc, el|
  item_exists = acc.any?  {|e| e[:name].eql? el[:name] }
  item_exists ? acc.find {|e| e[:name].eql? el[:name] }[:qty] += el[:qty]  : acc << el
  acc
end
2赞 Rajagopalan 4/16/2023 #2

输入

items = [
  {name: 'item_1', qty: 1, unit_price: 5},
  {name: 'item_1', qty: 2, unit_price: 5},
  {name: 'item_2', qty: 3, unit_price: 10},
  {name: 'item_2', qty: 2, unit_price: 10}
]

法典

result = items.group_by { |item| item[:name] }
              .values
              .map do |arr|
  arr.reduce do |h1, h2|
    h1.merge(h2) do |k1, v1, v2|
      k1.eql?(:qty) ? v1 + v2 : v1
    end
  end
end

输出

[{:name=>"item_1", :qty=>3, :unit_price=>5}, {:name=>"item_2", :qty=>5, :unit_price=>10}]
2赞 Ben Stephens 4/16/2023 #3

这使用双 splat 运算符来复制哈希值,并在首次填充累加器哈希中名称的条目时将数量设置为零。

items.reduce({}) do |acc, val|
  acc[val[:name]] ||= {**val, qty: 0} # initialise entry in accumulator hash for name
  acc[val[:name]][:qty] += val[:qty]  # add quantity to entry in accumulator hash
  acc
end.values

评论

0赞 Cary Swoveland 4/18/2023
这太酷了!我从没想过双飞溅如此多才多艺。
3赞 Cary Swoveland 4/17/2023 #4
items = [
  {name: 'item_1', qty: 1, unit_price: 5},
  {name: 'item_1', qty: 2, unit_price: 5},
  {name: 'item_2', qty: 3, unit_price: 10},
  {name: 'item_2', qty: 2, unit_price: 10}
]
items.each_with_object({}) do |g,h|
  h.update(g[:name]=>g) { |_k,o,n| o.merge(qty: o[:qty] + n[:qty]) }
end.values
  [{:name=>"item_1", :qty=>3, :unit_price=>5},
   {:name=>"item_2", :qty=>5, :unit_price=>10}]

这使用 Hash#update (aka ) 的形式,它使用块 () 来计算正在合并的两个哈希中存在的键的值。merge!{ |_k,o,n| o.merge(qty: o[:qty] + n[:qty]) }

g[:name]=>g,当它作为参数出现时(如此处),是 (并等同于) 的简写。包含公用键的块变量的名称以下划线开头,向下划线向读者发出信号,表明它未用于块计算。通常将此类块变量表示为简单(局部变量的有效名称)。{ g[:name]=>g }_k_

items没有突变。


我们可以添加一些语句来检查示例的计算。puts

items.each_with_object({}) do |g,h|
  puts "h = #{h}"
  puts "g = #{g}"
  puts "g[:name]=>g = #{g[:name]}=>#{g}"
  h.update(g[:name]=>g) do |_k,o,n|
    puts "Defer to block to calc value of key #{_k}"
    puts "o = #{o}"
    puts "n = #{n}"
    puts "{ qty: o[:qty] + n[:qty] } = #{{ qty: o[:qty] + n[:qty] }}"
    f = o.merge(qty: o[:qty] + n[:qty])
    puts "o.merge(qty: o[:qty] + n[:qty]) = #{f}"
    f
  end
  puts
end.tap do |h|
  puts "Value of h returned = #{h}"
  puts "h.values = #{h.values}"
end.
values
  #=> [{:name=>"item_1", :qty=>3, :unit_price=>5},
  #    {:name=>"item_2", :qty=>5, :unit_price=>10}]

以下是打印的(经过一些手工重新格式化)。

h = {}
g = {:name=>"item_1", :qty=>1, :unit_price=>5}
g[:name]=>g = item_1=>{:name=>"item_1", :qty=>1, :unit_price=>5}

h = {"item_1"=>{:name=>"item_1", :qty=>1, :unit_price=>5}}
g = {:name=>"item_1", :qty=>2, :unit_price=>5}
g[:name]=>g = item_1=>{:name=>"item_1", :qty=>2, :unit_price=>5}
Defer to block to calc value of key item_1
o = {:name=>"item_1", :qty=>1, :unit_price=>5}
n = {:name=>"item_1", :qty=>2, :unit_price=>5}
{ qty: o[:qty] + n[:qty] } = {:qty=>3}
o.merge(qty: o[:qty] + n[:qty]) = {:name=>"item_1", :qty=>3, :unit_price=>5}

h = {"item_1"=>{:name=>"item_1", :qty=>3, :unit_price=>5}}
g = {:name=>"item_2", :qty=>3, :unit_price=>10}
g[:name]=>g = item_2=>{:name=>"item_2", :qty=>3, :unit_price=>10}

h = {"item_1"=>{:name=>"item_1", :qty=>3, :unit_price=>5},
     "item_2"=>{:name=>"item_2", :qty=>3, :unit_price=>10}}
g = {:name=>"item_2", :qty=>2, :unit_price=>10}
g[:name]=>g = item_2=>{:name=>"item_2", :qty=>2, :unit_price=>10}
Defer to block to calc value of key item_2
o = {:name=>"item_2", :qty=>3, :unit_price=>10}
n = {:name=>"item_2", :qty=>2, :unit_price=>10}
{ qty: o[:qty] + n[:qty] } = {:qty=>5}
o.merge(qty: o[:qty] + n[:qty]) = {:name=>"item_2", :qty=>5, :unit_price=>10}

Value of h returned = 
  {"item_1"=>{:name=>"item_1", :qty=>3, :unit_price=>5},
   "item_2"=>{:name=>"item_2", :qty=>5, :unit_price=>10}}
h.values = [{:name=>"item_1", :qty=>3, :unit_price=>5},
            {:name=>"item_2", :qty=>5, :unit_price=>10}]

请注意,当 的第一个和第三个元素被传递到外部块时,不会参考 的块。那是因为 尚未包含密钥 .updateitemshg[:name]

参见 Object#tap

评论

1赞 Cary Swoveland 4/17/2023
@Ben,非常感谢您发现我的错误。注意它只是变异了,但它是错误的。我已经做了一个必要的修复,并将详细说明。顺便说一句,当我提供改变输入的解决方案时,OP对是否允许(我很少这样做)保持沉默,我遵循我认为是SO的一般做法,在我的回答中指出它改变了输入。items
0赞 jkvithanage 4/18/2023
感谢您提供更简单且解释清楚的解决方案。