计算哈希数组中多个值的最简洁方法?
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.inject
或 data.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}}
我有一个这样的哈希数组:
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.inject
或 data.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}}