Modifying the first value with #inject

Is there an idiom for doing something to the first value as part of an
inject? Or do I need to .map{}.inject{}?

class Array
  def sum
    inject(0){ |total, entry| total + entry.to_f }
  end
  def product
    inject(1){ |total, entry| total * entry.to_f }
  end
  def running_difference
    inject{ |total, entry| total - entry.to_f }
  end
  def running_divide
    inject{ |total, entry| total / entry.to_f }
  end
end

a1 = [5,4,3,2,1]
p a1.sum
#=> 15.0
p a1.product
#=> 120.0
p a1.running_difference
#=> -5.0
p a1.running_divide
#=> 0.208333333333333

a2 = ["5","4","3","2","1"]
p a2.sum
#=> 15.0
p a2.product
#=> 120.0
p a2.running_difference
#=> tmp.rb:9:in `running_difference': undefined method `-' for
"5":String (NoMethodError)
#=> from tmp.rb:36:in `inject'
#=> from tmp.rb:9:in `running_difference'
#=> from tmp.rb:31

Is there an idiom for doing something to the first value as part of an
inject? Or do I need to .map{}.inject{}?

class Array
  def sum
    inject(0){ |total, entry| total + entry.to_f }
  end
  def product
    inject(1){ |total, entry| total * entry.to_f }
  end
  def running_difference
    inject{ |total, entry| total - entry.to_f }

use
       inject(0) { |total, entry| total / entry.to_f }
or
       inject { |total, entry| total.to_f / entry.to_f }
here

  end
  def running_divide
    inject{ |total, entry| total / entry.to_f }

use
        inject{ |total, entry| total.to_f / entry.to_f }
here

  end
end

What's happening is that inject without the argument uses the first
element of the enumerable as the initial value, and skips it in the
iteration.

a2 = ["5","4","3","2","1"]
p a2.sum
#=> 15.0
p a2.product
#=> 120.0

These both work you are supplying the intial value of 0 and 1
respectively and the Numerics convert the arguments of +, and * to
numerics.

p a2.running_difference
#=> tmp.rb:9:in `running_difference': undefined method `-' for
"5":String (NoMethodError)
#=> from tmp.rb:36:in `inject'
#=> from tmp.rb:9:in `running_difference'
#=> from tmp.rb:31

Since you haven't specified an initial value for the inject here the
first iteration is trying to do

   "5" - "4".to_f

which doesn't work.

···

On 9/5/06, Phrogz <gavin@refinery.com> wrote:

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Phrogz wrote:

Is there an idiom for doing something to the first value as part of an
inject? Or do I need to .map{}.inject{}?

class Array
  def sum
    inject(0){ |total, entry| total + entry.to_f }
  end
  def product
    inject(1){ |total, entry| total * entry.to_f }
  end
  def running_difference
    inject{ |total, entry| total - entry.to_f }
  end
  def running_divide
    inject{ |total, entry| total / entry.to_f }
  end
end

Personally I would definitively not include those .to_f's because those make the code less useful. Consider for example an array that contains Bignums - you force them to floats and get imprecise float match as opposed to precise int math. Let #coerce() do it's work.

Also, Enumerable seems a better place for such general methods.

a1 = [5,4,3,2,1]
p a1.sum
#=> 15.0
p a1.product
#=> 120.0
p a1.running_difference
#=> -5.0
p a1.running_divide
#=> 0.208333333333333

a2 = ["5","4","3","2","1"]
p a2.sum
#=> 15.0
p a2.product
#=> 120.0
p a2.running_difference
#=> tmp.rb:9:in `running_difference': undefined method `-' for
"5":String (NoMethodError)
#=> from tmp.rb:36:in `inject'
#=> from tmp.rb:9:in `running_difference'
#=> from tmp.rb:31

IMHO the map or map! approach is better because you get better modularization. If you want the performance you can use inject directly - but for those general methods I would not have these conversions.

Oh, wait, you could do this:

module Enumerable
   def sum(*init,&b)
     b ||= lambda {|x| x}
     inject(*init) {|x,y| x + b[y]}
   end
end

irb(main):010:0> %w{1 2 3 4}.sum(0) {|x| x.to_i}
=> 10
irb(main):011:0> %w{1 2 3 4}.sum
=> "1234"

Or even

module Enumerable
   def agg(op,*init,&b)
     b ||= lambda {|x| x}
     inject(*init) {|x,y| x.send(op, b[y])}
   end
end

irb(main):019:0* %w{1 2 3 4}.agg(:*, 1) {|x| x.to_i}
=> 24
irb(main):020:0> %w{1 2 3 4}.agg(:+, 0) {|x| x.to_i}
=> 10
irb(main):022:0> %w{1 2 3 4}.agg(:+)
=> "1234"

Kind regards

  robert