Reputation: 22379
I have an array of Salary objects, such as:
Person 1, Job 1, 400
Person 1, Job 1, 300
Person 1, Job 1, 300
Person 1, Job 2, 500
I would like to aggregate them and sum the numbers, so that the result would be:
Person 1, Job 1, 1000
Person 1, Job 2, 500
How should I go about it?
Upvotes: 1
Views: 135
Reputation: 110685
Your array contains instances of a class. Given that class, @Rafa has answered your question. You probably have control over the class, but suppose you didn't? Suppose you were passed that array and someone else maintained the class. I'm not sure if that is good practice, but that is the situation I'd like to address.
Further, suppose you just want to determine the total salary for each :person/:job pair
(and not create instances of the class with instance variables having the desired values). Your array might look like this:
p salaries
#=> #<Salary:0x000001010cb230 @person=1, @job=1, @salary=400>,
#=> #<Salary:0x000001010cb1e0 @person=1, @job=1, @salary=300>,
#=> #<Salary:0x000001010cb190 @person=1, @job=1, @salary=300>,
#=> #<Salary:0x000001010cb140 @person=1, @job=2, @salary=500>]
It is evident that the class Salary has the three instance variables needed. (I generated this using @Rafa's class definition, but without attr_reader
and inspect
.)
Salaries.instance_methods(false) #=> []
tells us that there are no getters for these instance variables. We can retrieve their values, however, with Object#instance_variable_get. It is then a simple matter to aggregate salaries by person/job pair:
def aggregate_salaries(salaries)
salaries
.group_by { |e| [e.instance_variable_get(:@person),
e.instance_variable_get(:@job)] }
.map do |(person,job),ae|
tot_sal = ae.reduce(0) { |t,e| t + e.instance_variable_get(:@salary) }
{person: person, job: job, salary: tot_sal}
end
end
aggregate_salaries(salaries)
#=> [{:person=>1, :job=>1, :salary=>1000},
# {:person=>1, :job=>2, :salary=>500}]
Upvotes: 1
Reputation: 4860
class Salary
attr_reader :person, :job, :salary
def initialize(args={})
@person = args[:person]
@job = args[:job]
@salary = args[:salary]
end
def inspect
"<Person #{person}, Job #{job}, #{salary}>"
end
end
salaries = [
Salary.new({:person => 1, :job => 1, :salary => 400}),
Salary.new({:person => 1, :job => 1, :salary => 300}),
Salary.new({:person => 1, :job => 1, :salary => 300}),
Salary.new({:person => 1, :job => 2, :salary => 500})]
# => [<Person 1, Job 1, 400>, <Person 1, Job 1, 300>,
# <Person 1, Job 1, 300>, <Person 1, Job 2, 500>]
grouped_salaries = salaries.group_by do |salary|
[salary.person, salary.job]
end.map do |key, value|
Salary.new(person: key[0],
job: key[1],
salary: value.reduce(0) { |acc, s| acc + s.salary })
end
# => [<Person 1, Job 1, 1000>, <Person 1, Job 2, 500>]
Upvotes: 2