Modifying the first value with #inject

P

Phrogz

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
 
R

Rick DeNatale

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

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.
 
R

Robert Klemme

Phrogz said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,165
Latest member
JavierBrak
Top