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

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

我有一个这样的哈希数组:

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}
]

该组只会是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

我正在考虑将实际结果存储在嵌套哈希中,例如:

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

但这可能不是最好的方法,所以我愿意接受其他想法。

在 Ruby 中仅对数据进行一次迭代时最干净的方法是什么?以某种方式使用 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}

我会把转换成所需的格式留给你;-)

这种方式只会遍历哈希一次:

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}}

如果那确实是您想要的输出,那么像这样的东西就可以了:

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

然后

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

感谢@CarySwoveland 指出我的原始方法没有考虑没有通过或失败值的情况。现在已经解决了这个问题,因此像 [{ group: "A", result: 1 }] 这样的散列数组现在将显示 {a:{:pass => 1, :fail => 0}} 以前应该是 {a:{:pass => 1, :fail => nil}}.

的地方

您可以使用 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}}