根据ruby中的特定键/值对合并哈希值

时间:2014-03-27 23:47:55

标签: ruby arrays hash

我正在尝试根据特定的键/值对合并散列数组。

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为相同的组和其他键的总和。

我为之前的拼写错误而道歉。

1 个答案:

答案 0 :(得分: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表示。我们只想将它们加在一起。请注意,geach_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"}

<强>注释

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

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}