[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Modifying the first value with #inject

Gavin Kistner

9/5/2006 6:45:00 PM

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

2 Answers

Rick DeNatale

9/5/2006 7:23:00 PM

0

On 9/5/06, Phrogz <gavin@refinery.com> 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 }
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.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

Robert Klemme

9/5/2006 9:21:00 PM

0

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