【问题标题】:Merge hashes based on particular key/value pair in ruby基于 ruby​​ 中的特定键/值对合并哈希
【发布时间】:2014-03-31 12:54:23
【问题描述】:

我正在尝试基于特定键/值对合并哈希数组。

array = [ {:id => '1', :value => '2'}, {:id => '1', :value => '5'} ]

我希望输出是

{:id => '1', :value => '7'}

正如 patru 所说,在 sql 术语中,这相当于:

SELECT SUM(value) FROM Hashes GROUP BY id

换句话说,我有一个包含记录的哈希数组。我想获得特定字段的总和,但总和将按键/值对分组。换句话说,如果我的选择标准是 :id ,如上例所示,那么它会将散列分成 id 相同的组,并将其他键相加。

对于由于之前的错字而造成的任何混乱,我深表歉意。

【问题讨论】:

  • 您可能需要与您的键保持一致,它们是字符串还是符号
  • 数组中的所有哈希是否都有:id:value 键,并且id 总是1?
  • 我猜你想 sum 具有特定 :id 的散列的 :value 属性,就像在 SQL 语句 SELECT SUM(value) FROM Hashes GROUP BY id 中一样,如果你得到什么我是说。你能澄清一下吗?
  • 一个哈希中的键是符号,另一个是字符串。请更新您的问题。

标签: ruby arrays hash


【解决方案1】:

编辑:自从我第一次发布我的答案以来,这个问题已经得到澄清。因此,我大幅修改了我的答案。

这里有两种解决这个问题的“标准”方法。两者都使用Enumerable#select 首先从包含给定键/值对的数组(哈希)中提取元素。

#1

第一种方法使用Hash#merge! 将每个数组元素(哈希)顺序合并成一个初始为空的哈希。

代码

def doit(arr, target_key, target_value)
  qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
  return nil if qualified.empty?  
  qualified.each_with_object({}) {|h,g|
    g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
end

示例

arr = [{:id  => '1', :value => '2'}, {:id => '2', :value => '3'},
       {:id  => '1', :chips => '4'}, {:zd => '1', :value => '8'},
       {:cat => '2', :value => '3'}, {:id => '1', :value => '5'}]

doit(arr, :id, '1')
  #=> {:id=>"1", :value=>"7", :chips=>"4"}

说明

这里的关键是使用Hash#merge! 的版本,它使用一个块来确定每个键/值对的值,其键出现在要合并的两个哈希中。该键的两个值在上面由块变量hvgv 表示。我们只是想把它们加在一起。请注意,g 是由each_with_object 创建并由doit 返回的(最初为空的)哈希对象。

target_key = :id
target_value = '1'

qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
  #=> [{:id=>"1", :value=>"2"},{:id=>"1", :chips=>"4"},{:id=>"1", :value=>"5"}] 
qualified.empty?
  #=> false 
qualified.each_with_object({}) {|h,g|
  g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
  #=> {:id=>"1", :value=>"7", :chips=>"4"}

#2

进行此类计算的另一种常用方法是使用Enumerable#flat_map,然后使用Enumerable#group_by

代码

def doit(arr, target_key, target_value)
  qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
  return nil if qualified.empty?  
  qualified.flat_map(&:to_a)
           .group_by(&:first)
           .values.map { |a| a.first.first == target_key ? a.first :
             [a.first.first, a.reduce(0) {|tot,s| tot + s.last}]}.to_h
end

说明

这可能看起来很复杂,但如果你把它分解成几个步骤,它还不错。这是正在发生的事情。 (qualified的计算与#1相同。)

target_key = :id
target_value = '1'

c = qualified.flat_map(&:to_a)
  #=> [[:id,"1"],[:value,"2"],[:id,"1"],[:chips,"4"],[:id,"1"],[:value,"5"]] 
d = c.group_by(&:first)
  #=> {:id=>[[:id, "1"], [:id, "1"], [:id, "1"]],
  #    :value=>[[:value, "2"], [:value, "5"]],
  #    :chips=>[[:chips, "4"]]} 
e = d.values
  #=> [[[:id, "1"], [:id, "1"], [:id, "1"]],
  #    [[:value, "2"], [:value, "5"]],
  #    [[:chips, "4"]]] 
f = e.map { |a| a.first.first == target_key ? a.first :
  [a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }
  #=> [[:id, "1"], [:value, "7"], [:chips, "4"]]
f.to_h => {:id=>"1", :value=>"7", :chips=>"4"}
  #=> {:id=>"1", :value=>"7", :chips=>"4"}

评论

您可能希望考虑在哈希整数中设置值并从qualified 中排除target_key/target_value 对:

arr = [{:id  => 1, :value => 2}, {:id => 2, :value => 3},
       {:id  => 1, :chips => 4}, {:zd => 1, :value => 8},
       {:cat => 2, :value => 3}, {:id => 1, :value => 5}]

target_key = :id
target_value = 1

qualified = arr.select { |h| h.key?(target_key) && h[target_key]==target_value}
               .each { |h| h.delete(target_key) }
  #=> [{:value=>2}, {:chips=>4}, {:value=>5}] 
return nil if qualified.empty?  

然后

qualified.each_with_object({}) {|h,g| g.merge!(h) { |k,gv,hv| gv + hv } }
  #=> {:value=>7, :chips=>4}

qualified.flat_map(&:to_a)
         .group_by(&:first)
         .values
         .map { |a| [a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }.to_h
  #=> {:value=>7, :chips=>4}

【讨论】:

    猜你喜欢
    • 2015-06-13
    • 1970-01-01
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    • 1970-01-01
    相关资源
    最近更新 更多