为什么以下代码会发出错误?
['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
如果传递了初始值,则可以正常工作:
['hello','stack','overflow'].inject(0){|memo,s|memo+s.length}
=> 18
答案 0 :(得分:16)
答案 1 :(得分:6)
正如错误消息已经告诉您的那样,问题是您有一个TypeError
。仅仅因为Ruby被动态地和隐式地输入并不意味着你不必考虑类型。
没有显式累加器的Enumerable#inject
类型(通常称为reduce
)类似于
reduce :: [a] → (a → a → a) → a
或者用我编写的更多Rubyish符号
Enumerable[A]#inject {|A, A| A } → A
您会注意到所有类型都是相同的。 Enumerable
的元素类型,块的两个参数类型,块的返回类型以及整个方法的返回类型。
在您的情况下,块的类型不会加起来。该块传递了两个String
,它应该返回String
。但是,您在第一个参数(+
)上调用String
方法,其参数为Integer
。但是String#+
不接受Integer
它只需要String
或更精确的东西可以转换到String
,即某事响应#to_str
。这就是为TypeError
获得String#+
的原因。
带有显式累加器的Enumerable#inject
的类型(通常称为fold
)类似于
fold :: [b] → a → (a → b → a) → a
或
Enumerable[B]#inject(A) {|A, B| A } → A
在这里,您可以看到累加器可以具有不同的类型,而不是集合的元素类型。这正是你所需要的。
这两条规则通常可以解决所有与Enumerable#inject
相关的问题:
当您执行
之类的操作时,规则#1通常会咬你acc[key] = value
在您的块中,因为赋值评估为指定的值,而不是赋值的接收者。你必须用
替换它acc.tap { acc[key] = value }
在您的特定情况下,已经提到了两种解决方案。使用显式累加器
ary.reduce(0){|acc, e| acc + e.length }
或首先转换为整数
ary.map(&:length).reduce(:+)
答案 2 :(得分:3)
没有初始值,inject
使用集合中的第一个元素作为初始值。
请参阅ruby-doc。