[英]How do I group and add values from nested hashes and arrays with same key?
I am trying to get the sum of points and average grade for each student inside this combination of hashes and arrays but all my attempts only return the general sum for all entries.我试图在这种散列和数组的组合中获得每个学生的总分和平均成绩,但我所有的尝试只返回所有条目的总和。 Any ideas?
有任何想法吗?
student_data =
{"ST4"=>[{:student_id=>"ST4", :points=> 5, :grade=>5},
{:student_id=>"ST4", :points=>10, :grade=>4},
{:student_id=>"ST4", :points=>20, :grade=>5}],
"ST1"=>[{:student_id=>"ST1", :points=>10, :grade=>3},
{:student_id=>"ST1", :points=>30, :grade=>4},
{:student_id=>"ST1", :points=>45, :grade=>2}],
"ST2"=>[{:student_id=>"ST2", :points=>25, :grade=>5},
{:student_id=>"ST2", :points=>15, :grade=>1},
{:student_id=>"ST2", :points=>35, :grade=>3}],
"ST3"=>[{:student_id=>"ST3", :points=> 5, :grade=>5},
{:student_id=>"ST3", :points=>50, :grade=>2}]}
The desired hash can be obtained thusly.可以这样获得所需的散列。
student_data.transform_values do |arr|
points, grades = arr.map { |h| h.values_at(:points, :grade) }.transpose
{ :points=>points.sum, :grades=>grades.sum.fdiv(grades.size) }
end
#=> {"ST4"=>{:points=>35, :grades=>4.666666666666667},
# "ST1"=>{:points=>85, :grades=>3.0},
# "ST2"=>{:points=>75, :grades=>3.0},
# "ST3"=>{:points=>55, :grades=>3.5}}
The first value passed to the block is the value of the first key, 'ST4'
and the block variable arr
is assigned that value:传递给块的第一个值是第一个键的值
'ST4'
并且块变量arr
被分配了该值:
a = student_data.first
#=> ["ST4",
# [{:student_id=>"ST4", :points=> 5, :grade=>5},
# {:student_id=>"ST4", :points=>10, :grade=>4},
# {:student_id=>"ST4", :points=>20, :grade=>5}]
# ]
arr = a.last
#=> [{:student_id=>"ST4", :points=> 5, :grade=>5},
# {:student_id=>"ST4", :points=>10, :grade=>4},
# {:student_id=>"ST4", :points=>20, :grade=>5}]
The block calculations are as follows.块计算如下。 The first value of
arr
passed by map
to the inner block is map
传递给内部块的arr
的第一个值是
h = arr.first
#=> {:student_id=>"ST4", :points=>5, :grade=>5}
h.values_at(:points, :grade)
#=> [5, 5]
After the remaining two elements of arr
are passed to the block we have在将
arr
的其余两个元素传递给块后,我们有
b = arr.map { |h| h.values_at(:points, :grade) }
#=> [[5, 5], [10, 4], [20, 5]]
Then然后
points, grades = b.transpose
#=> [[5, 10, 20], [5, 4, 5]]
points
#=> [5, 10, 20]
grades
#=> [5, 4, 5]
We now simply form the hash that is the value of 'ST4'
.我们现在简单地形成哈希值,即
'ST4'
的值。
c = points.sum
#=> 35
d = grades.sum
#=> 14
e = grades.size
#=> 3
f = c.fdiv(d)
#=> 4.666666666666667
The value of 'ST4'
in student_data
therefore maps to the hash因此,
student_data
中'ST4'
的值映射到哈希
{ :points=>c, :grades=>f }
#=> {:points=>35, :grades=>4.666666666666667}
The mappings of the remaining keys of student_data
are computed similarly. student_data
其余键的映射也是类似计算的。
See Hash#transform_values , Enumerable#map , Hash#values_at , Array#transpose , Array#sum and Integer#fdiv .参见Hash#transform_values 、 Enumerable#map 、 Hash#values_at 、 Array#transpose 、 Array#sum和Integer#fdiv 。
Whatever you expect can be achieved as below,无论您期望什么都可以实现,如下所示,
student_data.values.map do |z|
z.group_by { |x| x[:student_id] }.transform_values do |v|
{
points: v.map { |x| x[:points] }.sum, # sum of points
grade: (v.map { |x| x[:grade] }.sum/v.count.to_f).round(2) # average of grades
}
end
end
As exact expected output format is not specified, obtained in following way,由于没有指定确切的预期输出格式,通过以下方式获得,
=> [
{"ST4"=>{:points=>35, :grade=>4.67}},
{"ST1"=>{:points=>85, :grade=>3.0}},
{"ST2"=>{:points=>75, :grade=>3.0}},
{"ST3"=>{:points=>55, :grade=>3.5}}
]
For Ruby 2.6 using Object#then
orObject#yield_self
for Ruby 2.5对于Ruby 2.6使用
Object#then
或Object#yield_self
for Ruby 2.5
student_data.transform_values { |st| st
.each_with_object(Hash.new(0)) { |h, hh| hh[:sum_points] += h[:points]; hh[:sum_grade] += h[:grade]; hh[:count] += 1.0 }
.then{ |hh| {tot_points: hh[:sum_points], avg_grade: hh[:sum_grade]/hh[:count] } }
}
Given the array for each student:给定每个学生的数组:
st = [{:student_id=>"ST4", :points=> 5, :grade=>5}, {:student_id=>"ST4", :points=>10, :grade=>4}, {:student_id=>"ST4", :points=>20, :grade=>5}]
First build a hash adding and counting using Enumerable#each_with_object
with a Hash#default
set at zero (Hash.new(0)
)首先使用
Enumerable#each_with_object
构建哈希添加和计数, Hash#default
Enumerable#each_with_object
设置为零(Hash.new(0)
)
step1 = st.each_with_object(Hash.new(0)) { |h, hh| hh[:sum_points] += h[:points]; hh[:sum_grade] += h[:grade]; hh[:count] += 1.0 } #=> {:sum_points=>35, :sum_grade=>14, :count=>3.0}
Then use then!那就用吧! (
yield_self
for Ruby 2.5) ( Ruby 2.5 的
yield_self
)
step2 = step1.then{ |hh| {tot_points: hh[:sum_points], avg_grade: hh[:sum_grade]/hh[:count] }} #=> {:tot_points=>35, :avg_grade=>4.666666666666667}
Put all together using Hash#transform_values
as in the first snippet of code使用
Hash#transform_values
将所有内容放在一起,就像在第一个代码片段中一样
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.