質問

I have something like this:

a = [{"group_id" => 1, "student_id" => 3, "candies" => 4},
  {"group_id" => 2, "student_id" => 1, "candies" => 3},
  {"group_id" => 1, "student_id" => 2, "candies" => 2},
  {"group_id" => 3, "student_id" => 4, "candies" => 6},
  {"group_id" => 1, "student_id" => 5, "candies" => 1},
  {"group_id" => 3, "student_id" => 6, "candies" => 1},
  {"group_id" => 4, "student_id" => 8, "candies" => 3}]

I have three groups of students and each student gets a certain number of candies. I wish to count the total number of candies in a group. For that, I need to know the students which belong to a certain group and accumulate their candy count. I can do it using loops and initializing counts to zero:

aa = a.group_by { |a| a["group_id"] }
# =>
{
  1 => [
    {"group_id"=>1, "student_id"=>3, "candies"=>4},
    {"group_id"=>1, "student_id"=>2, "candies"=>2},
    {"group_id"=>1, "student_id"=>5, "candies"=>1}
  ],
  2 => [{"group_id"=>2, "student_id"=>1, "candies"=>3}],
  3 => [
    {"group_id"=>3, "student_id"=>4, "candies"=>6},
    {"group_id"=>3, "student_id"=>6, "candies"=>1}
  ],
  4 => [{"group_id"=>4, "student_id"=>8, "candies"=>3}]
}

But I'm not able to accumulate the values within the group_id. I wonder if there are any succinct ways of representing it. How do I sum the total number of candies that are present in a group?

役に立ちましたか?

解決

The first your step (grouping) is correct. After that you can use the following:

a.group_by {|g| g['group_id']}.map do |g, students|
  {group_id:g, candies:students.map {|st| st['candies']}.inject(&:+)}
end

map function is often used with collections instead of loops to make some operation on each element and return modified version of the collection.

Output:

[{:group_id=>1, :candies=>7}, 
 {:group_id=>2, :candies=>3}, 
 {:group_id=>3, :candies=>7}, 
 {:group_id=>4, :candies=>3}]

他のヒント

Adding to @StasS answer, a more direct hash way to do (with a more cryptic code) is like this:

> Hash[a.group_by{|g| g['group_id']}.map{|g,s| [g, s.inject(0){|a,b| a + b["candies"]}]}]
 => {1=>7, 2=>3, 3=>7, 4=>3} 

you can unfold the line like this:

groups = a.group_by{|g| g['group_id']}
id_candies_pairs = groups.map{|g,s| [g, s.inject(0){|a,b| a + b["candies"]}]}
id_candies_hash = Hash[id_candies_pairs]
return id_candies_hash

Riffing on the answer by @StasS, you can also just build a simpler looking hash like:

totals_by_group_id = {}  
a.group_by {|g| g['group_id']}.map do |g, students|  
  totals_by_group_id[g] = students.map {|st| st['candies']}.inject(&:+)  
end

The resulting totals_by_group_id hash is:

{1=>7, 2=>3, 3=>7, 4=>3}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top