Was looking around for a quickie function to do an average on an Enumerable/Array just like max and min - and found there isn't one. So here's a quick-n-dirty one that you can drop into config/environment.rb. Examples for use in the comments:
module Enumerable
# returns a simple average of each item
# If a block is passed - it will try to perform the operation on the item.
# if the result is nil - it won't be counted towards the average.
# Egs:
# [1,2,3].average == 2
# ["a","ab","abc"].average &:length == 2
# @purchases.average &:item_price == 10.45
def average(&block)
the_sum = 0
total_count = 0
self.each do |item|
# either the actual item, or a method called on the item
next_value = block_given? ? yield(item) : item
unless next_value.nil?
the_sum += next_value
total_count += 1
end
end
return nil unless total_count > 0
the_sum / total_count
end
end
I used it for displaying neat averages in the view thus:
<p>Averages:
<br />Number of items: <%= @purchases.average &:num_items -%>
<br />Unit price: <%= @purchases.average &:price -%>
<br />Total price: <%= @purchases.average {|p| p.num_items * p.price } -%> </p>
5 comments:
Removed the temporary variables - the last line is a bit more golfed than I'd ideally imagine, though...
http://pastie.org/private/okipyyrpy4xuz9tcuwsg
Cool! That's a nice refactor. I hadn't bothered to refactor this one for brevity yet.
Sometimes I wonder if it's beneficial to have the long-winded version on an explanatory blogpost - but most people that read this stuff know what they're doing... so yeah, yours is better ;)
Hmm - on second thoughts - this code compacts before sending the items through the block-code (which may yield a nil)... which was why I did the whole bodgy thing with the total_count to start with.
If we fix this by returning an array of the items then compacting/summing - we'd be able to get the true "total_count" but we'd have potential memory issues... hmmm - guess it depends on what this will be used for.
Any ideas?
Well... first we'd have to decide on the semantics of total_count - I thought it's the # of elements in the original array, it didn't even occur to me that some nils can pop up after yield()ing...
Ya - I guess one of the examples I didn't show was something along the lines of:
Widget.find(:all).average &:price
Which is essentially what led to this implementation. In this instance there might not be a price specified - so it'd be nice if it didn't use that in generating the average.
Post a Comment