Why the following code issues an error ?
['hello','stack','overflow'].inject{|memo,s|memo+s.length}
TypeError: can't convert Fixnum into String
from (irb):2:in `+'
from (irb):2:in `block in irb_binding'
from (irb):2:in `each'
from (irb):2:in `inject'
from (irb):2
If the initial value is passed, it works OK:
['hello','stack','overflow'].inject(0){|memo,s|memo+s.length}
=> 18
You have the answer in apidock :
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.
That is, without an initial value, you're trying to do 'hello' + 'stack'.length
As the error message already tells you, the problem is that you have a TypeError
. Just because Ruby is dynamically and implicitly typed doesn't mean that you don't have to think about types.
The type of Enumerable#inject
without an explicit accumulator (this is usually called reduce
) is something like
reduce :: [a] → (a → a → a) → a
or in a more Rubyish notation I just made up
Enumerable[A]#inject {|A, A| A } → A
You will notice that all the types are the same. The element type of the Enumerable
, the two argument types of the block, the return type of the block and the return type of the overall method.
In your case, the types for the block just don't add up. The block gets passed two String
s and it is supposed to return a String
. But you call the +
method on the first argument (which is a String
) with an argument that is an Integer
. But String#+
doesn't take an Integer
it only takes a String
or more precisely something which can be converted to a String
, i.e. something that responds to #to_str
. That's why you get a TypeError
for String#+
.
The type of Enumerable#inject
with an explicit accumulator (this is usually called fold
) is something like
fold :: [b] → a → (a → b → a) → a
or
Enumerable[B]#inject(A) {|A, B| A } → A
Here you see that the accumulator can have a different type than the element type of the collection. Which is precisely what you need.
These two rules generally get you through all Enumerable#inject
-related problems:
Rule #1 will most often bite you when you do something like
acc[key] = value
in your block, because assignments evaluate to the assigned value, not the receiver of the assignment. You'll have to replace this with
acc.tap { acc[key] = value }
In your particular case, the two solutions have already been mentioned. Either use an explicit accumulator
ary.reduce(0){|acc, e| acc + e.length }
or convert to integers first
ary.map(&:length).reduce(:+)
Without the initial value, inject
uses the first element in the collection as the initial value.
see ruby-doc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With