18

I need to sum values in an array hashes and I found a way to do it here

but it sure seems like there should be a more elegant way in Ruby.

Here is what works;

sales = [{"sale_price"=>210000, "deed_type"=>"Warranty Deed"}, {"sale_price"=>268300, "deed_type"=>"Warranty Deed Joint"}]

total_sales = sales.inject(0) {|sum, hash| sum + hash["sale_price"]}

The totals line is not very readable. It would be nice if something like this worked;

total_sales = sales.sum("sale_price")

Is this just wishful thinking or am I overlooking a better solution?

4
  • 2
    Its a correct way to do it. Crude way you can do: sum = 0;sales.each{|x| sum=sum+x["sale_price"]};sum Commented Feb 1, 2013 at 20:51
  • This belongs on codereview.stackexchange.com Commented Feb 1, 2013 at 21:06
  • Thanks for the tip Tin Man, I wasn't aware of the code review site. Commented Feb 1, 2013 at 21:10
  • "It would be nice if something like this worked". This is Ruby, add the Enumerable#sum you'd like and problem solved. Maybe you should call it Enumerable#hash_sum though, Ruby is a OOP language and sum should be calling methods. Commented Feb 1, 2013 at 21:16

2 Answers 2

40

I like using the map/reduce metaphor like so:

total_sales = sales.map {|s| s['sale_price']}.reduce(0, :+)

The reduce method is a synonym for the inject method, I find the name inject to be somewhat confusing with the memo component. It has another form I use above to take the initial value and a reference to a method call used for the combination/reduction process.

I think the overall pattern of mapping the values and then reducing them to an aggregate is well known and self-documenting.

EDIT: Use symbol :+ instead of proc reference &:+

Sign up to request clarification or add additional context in comments.

6 Comments

Great answer. The Symbol.to_proc (&:) used as the second reduce argument is around an order-of-magnitude slower than just writing a plain block .reduce(0){|t,i| t + i }, only worth noting if your array may contain large numbers of elements. See igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri #5.
Wow, good to know. It's amazing what you find when you profile code or dig under the covers. Thanks for the info.
Thanks for the explanation. I am still trying to get my head around map/reduce which is probably why the intent of the code is not intuitive to me. Do you have any recommendations for a good explanation?
The way I read the code is: map a list of sales objects onto a list of sale prices and then reduce them by combining the prices into a total.
Under Rails, you can just go sales.map {|s| s['sale_price']}.sum. If s was an object with a sale_price method, it could be even terser: sales.sum(&:sale_price) :D
|
4

You can make it work:

sales = [{"sale_price"=>210000, "deed_type"=>"Warranty Deed"}, {"sale_price"=>268300, "deed_type"=>"Warranty Deed Joint"}]

def sales.sum(by)
  inject(0){|sum, h| sum + h[by]}
end

p sales.sum("sale_price") #=> 478300

Note this sum method (sum_by might be a better name) is not defined on Array, but only on the specific sales array.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.