views:

148

answers:

2

I am creating a rails app and have used this code in one of my methods

item_numbers.inject(0) {|sum, i| sum + i.amount}

item_numbers is an array of objects from my item_numbers table. The .amount method that I apply to them looks up the value of an item_number in a separate table and returns it as a BigDecimal object. Obviously the inject method then adds all of the returned i.amount objects and this works just fine.

I am just curious as to why it didn't work when I wrote this statement as

item_numbers.inject {|sum, i| sum + i.amount}

According to my trusty pickaxe book these should be equivalent. Is it because i.amount is a BigDecimal? If so, why does it now work? If not, then why doesn't it work.

+3  A: 

It's because you are accessing i.amount as opposed to just plain i. In the version that doesn't work, you're implicitly doing item_numbers[0] + item_numbers[1].amount + ....

One shorthand would be item_numbers.map(&:amount).inject(&:+), but that way can result in two iterations over the list, if map doesn't return an enumerator.

If that didn't convince you, look at what gets printed out if we define a method amount on Fixnum that prints the value before returning it:

irb(main):002:1>   def amount
irb(main):003:2>     puts "My amount is: #{self}"
irb(main):004:2>     return self
irb(main):005:2>   end
irb(main):006:1> end
=> nil
irb(main):007:0> [1,2,3].inject { |sum, i| sum + i.amount }
My amount is: 2
My amount is: 3
=> 6
irb(main):008:0> [1,2,3].inject(0) { |sum, i| sum + i.amount }
My amount is: 1
My amount is: 2
My amount is: 3
=> 6
irb(main):009:0>

We can see clearly that amount is not called on the first element when a starting value is not explicitly passed in.

Mark Rushakoff
+1 for clear exemple which illustrates the behaviour perfectly
Jean
+4  A: 

What we can read in API:

If you do not explicitly specify an initial value for memo, then uses the first element of collection is used as the initial value of memo.

So item_numbers[0] will be specified as an initial value - but it is not a number, it is an object. So we have got an error

undefined method `+'.

So we have to specify initial value as 0

item_numbers.inject(0){ |sum, i| sum + i }

fl00r
You can do a little test (10..15).inject do |sum, i| p sum sum+i endit will return:10, 21, 33, 46, 60=> 75As you can see _sum_ gets first item from array as initial value
fl00r
That explains it nicely. Thanks.
brad