简体   繁体   English

最简单的方法来计算哈希数组中的多个值?

[英]Cleanest way to count multiple values in an array of hashes?

I have an array of hashes like this: 我有这样一个哈希数组:

data = [
 {group: "A", result: 1},
 {group: "B", result: 1},
 {group: "A", result: 0},
 {group: "A", result: 1}, 
 {group: "B", result: 1},
 {group: "B", result: 1}, 
 {group: "B", result: 0},
 {group: "B", result: 0}
]

The group will only be either A or B, and the result will only be 1 or 0. I want to count how many times the result is 0 or 1 for each group, ie, to get a tally like so: 该组将仅是A或B,结果将仅为1或0。我想计算每个组的结果为0或1的次数,即得到一个类似的计数:

A: result is "1" 2 times
   result is "0" 1 time
B: result is "1" 3 times
   result is "0" 2 times

I am thinking of storing the actual results in a nested hash, like: 我正在考虑将实际结果存储在嵌套哈希中,例如:

{ a: { pass: 2, fail: 1 }, b: { pass: 3, fail: 2 } }

but this might not be the best way, so I'm open to other ideas here. 但这可能不是最好的方法,因此我对这里的其他想法持开放态度。

What would be the cleanest way to do this in Ruby while iterating over the data only once? 在仅迭代一次数据的Ruby中,最干净的方法是什么? Using data.inject or data.count somehow? 使用data.injectdata.count以某种方式?

stats = Hash[data.group_by{|h| [h[:group], h[:result]] }.map{|k,v| [k, v.count] }]
#=> {["A", 1]=>2, ["B", 1]=>3, ["A", 0]=>1, ["B", 0]=>2}

I'll leave the transformation to the desired format up to you ;-) 我将转换为所需的格式交给您;-)

This way would go over the hash only one time: 这种方式只会遍历散列一次:

result = Hash.new { |h, k| h[k] = { pass: 0, fail: 0 }}
data.each do |item|
  result[item[:group]][item[:result] == 0 ? :fail : :pass] += 1
end
result
# => {"A"=>{:pass=>2, :fail=>1}, "B"=>{:pass=>3, :fail=>2}}

If that is truely your desired output then something like this would work: 如果这确实是您想要的输出,则可以执行以下操作:

def pass_fail_hash(a=[],statuses=[:pass,:fail])
  a.map(&:dup).group_by{|h| h.shift.pop.downcase.to_sym}.each_with_object({}) do |(k,v),obj|
    obj[k] = Hash[statuses.zip(v.group_by{|v| v[:result]}.map{|k,v| v.count})]
    statuses.each {|status| obj[k][status] ||= 0 }
  end
end

Then 然后

pass_fail_hash data
#=>  {:a=>{:pass=>2, :fail=>1}, :b=>{:pass=>3, :fail=>2}}

Thank you to @CarySwoveland for pointing out my original method did not take into account cases where there were no passing or failing values. 感谢@CarySwoveland指出我的原始方法没有考虑没有传递或失败值的情况。 This has now been resolved so that a hash array like [{ group: "A", result: 1 }] will now show {a:{:pass => 1, :fail => 0}} where it would have previously been {a:{:pass => 1, :fail => nil}} . 现在,此问题已解决,因此像[{ group: "A", result: 1 }]这样的哈希数组现在将显示{a:{:pass => 1, :fail => 0}}在以前的位置{a:{:pass => 1, :fail => nil}}

You could use the form of Hash#update (same as Hash#merge! ) that takes a block to determine the values of keys that are contained in both hashes being merged: 您可以使用Hash#update的形式(与Hash#merge!相同),该形式采用一个块来确定要合并的两个哈希中包含的键的值:

data.map(&:values).each_with_object({}) { |(g,r),h|
  h.update({g.to_sym=>{pass: r, fail: 1-r } }) { |_,oh,nh|
   { pass: oh[:pass]+nh[:pass], fail: oh[:fail]+nh[:fail] } } }
  #=> {:A=>{:pass=>2, :fail=>1}, :B=>{:pass=>3, :fail=>2}}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM