4

Assuming the @houses array is set up as follows:

house1.price = 10
house2.price = 20
house3.price = 30
@houses << house1
@houses << house2
@houses << house3

This is the starting point of our calculation and we want to find the average price of a house:

total = 0
average = 0
for h in @houses
 total += h.price
end
average = total/@houses.size

This seems like quite a lot of typing just to get an average.

Is there a better way?

M. Pelosi
  • 43
  • 3

3 Answers3

14

Use the inject method on an enumerable collection. Inject lets you pass in an initial value for an 'accumulator' (0 in this case), then apply some operation to each element in the list, and return a new value for the accumulator to be passed into the next iteration.

The final value of the accumulator is then returned from the inject call.

So in this case, we just add up all the house prices into the accumulator, then finally divide by the total number.

You can get funkier and probably compress it down more with some Ruby skillz, but this is reasonably understandable, and only iterates through the list once to add up the values.

@houses.inject(0){|total, house| total + house.price} / @houses.size
madlep
  • 47,370
  • 7
  • 42
  • 53
11

Madlep's answer will work in any version of Ruby from the past several years. But if you're using Ruby 1.8.7 or later, you can express it a little bit more concisely as @houses.collect(&:price).inject(:+)/@houses.size.

Incidentally, I had assumed this technique would be slower since it has to loop twice, but thanks to optimizations in Ruby itself, it's actually much faster up to a couple million items in my tests on Ruby 1.8.7, and faster (but not by as much) even past 10 million items in Ruby 1.9. Goes to show you the importance of profiling.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • Hi Chuck. I've been looking in "The Ruby Programming Language" for an explanation of the syntax you gave here but I can't figure out what those special characters are for. Would you mind telling me what &:price means, as well as inject(:+) Thanks. – M. Pelosi Jul 23 '09 at 02:47
  • 6
    It's new in Ruby 1.8.7 and 1.9. For any method that takes a block, you can pass `&:somemethod` and it's equivalent to writing `{|object| object.somemethod}`. As for the `:+`, it tells inject to call `+` on all the objects in the array and accumulate the result, just like writing `inject{|memo, obj| memo+obj}`. Does that make sense? – Chuck Jul 23 '09 at 03:03
  • 1
    Didn't know they'd added that in 1.9. Awesome :D – madlep Jul 23 '09 at 03:20
  • Ah. I get it. I found some examples in the ruby doc section on inject + reduce. Thanks for explaining. I'd love to use this but sadly I can't upgrade my project to 1.87. – M. Pelosi Jul 23 '09 at 03:34
  • 1
    I think madlep's answer is perfectly good. But if you ever do want to use the `&:methodname` syntax in an older Ruby, you can just implement: `class Symbol; def to_proc() @proc_version ||= proc {|r, *a| r.send self, *a} end end`. It won't be as fast as 1.9's version, but it's functionally the same. – Chuck Jul 23 '09 at 08:37
0

If you're in a Rails app, you can do array.sum/array.size

  • `.sum` only works if it's an array of numbers. If you read the question, the OP has an array of house objects, not numbers. – Lambda Fairy Nov 29 '11 at 02:09