基于红宝石中的特定键/值对合并哈希 [英] Merge hashes based on particular key/value pair in ruby
问题描述
我正在尝试基于特定的键/值对合并哈希数组.
I am trying to merge an array of hashes based on a particular key/value pair.
array = [ {:id => '1', :value => '2'}, {:id => '1', :value => '5'} ]
我希望输出为
{:id => '1', :value => '7'}
正如patru所说,用sql术语等同于:
As patru stated, in sql terms this would be equivalent to:
SELECT SUM(value) FROM Hashes GROUP BY id
换句话说,我有一个包含记录的哈希数组.我想获取特定字段的总和,但是总和将按键/值对分组.换句话说,如果如上例所示,我的选择标准是:id,则它将散列分为ID相同且其他键求和的散列.
In other words, I have an array of hashes that contains records. I would like to obtain the sum of a particular field, but the sum would grouped by key/value pairs. In other words, if my selection criteria is :id as in the example above, then it would seperate the hashes into groups where the id was the same and the sum the other keys.
对于先前由于错字引起的任何困惑,我深表歉意.
I apologize for any confusion due to the typo earlier.
推荐答案
编辑:自从我第一次发布答案以来,这个问题就已经澄清了.结果,我对答案做了实质性的修改.
Edit: The question has been clarified since I first posted my answer. As a result, I have revised my answer substantially.
以下是解决此问题的两种标准"方法.两者都首先使用 Enumerable#select 从包含给定键/值对的数组(哈希)中提取元素.
Here are two "standard" ways of addressing this problem. Both use Enumerable#select to first extract the elements from the array (hashes) that contain the given key/value pair.
#1
第一种方法使用 Hash#merge! 以将每个数组元素(哈希)顺序合并到最初为空的哈希中.
The first method uses Hash#merge! to sequentially merge each array element (hashes) into a hash that is initially empty.
代码
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!
的版本,该版本使用一个块来确定每个键/值对的值,这些键/值对的键都出现在要合并的两个哈希中.该键的两个值在上面用块变量hv
和gv
表示.我们只是想将它们添加在一起.请注意,g
是由each_with_object
创建并由doit
返回的(最初为空)哈希对象.
The key here is to use the version of Hash#merge!
that uses a block to determine the value for each key/value pair whose key appears in both of the hashes being merged. The two values for that key are represented above by the block variables hv
and gv
. We simply want to add them together. Note that g
is the (initially empty) hash object created by each_with_object
, and returned by 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 ,后跟可枚举#group_by .
The other common way to do this kind of calculation is to use Enumerable#flat_map, followed by 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中的计算相同.)
This may look complex, but it's not so bad if you break it down into steps. Here's what's happening. (The calculation of qualified
is the same as in #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
对:
You may wish to consider makin the values in the hashes integers and exclude the target_key/target_value
pairs from 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}
这篇关于基于红宝石中的特定键/值对合并哈希的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!