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