我有代码:
class Scene
def initialize(number)
@number = number
end
attr_reader :number
end
scenes = [Scene.new("one"), Scene.new("one"), Scene.new("two"), Scene.new("one")]
groups = scenes.inject({}) do |new_hash, scene|
new_hash[scene.number] = [] if new_hash[scene.number].nil?
new_hash[scene.number] << scene
end
当我发动它时,我得到错误:
freq.rb:11:in `[]': can't convert String into Integer (TypeError)
from freq.rb:11:in `block in <main>'
from freq.rb:10:in `each'
from freq.rb:10:in `inject'
from freq.rb:10:in `<main>'
如果我将场景改为:
scenes = [Scene.new(1), Scene.new(1), Scene.new(2), Scene.new(1)]
问题消失了。
为什么我在第一种情况下收到错误消息?为什么Ruby决定将scene.number从String转换为Integer?
还有一个关于'注入'方法的问题。当Ruby初始化'new_hash'变量时,Ruby如何知道这个变量的类型?
答案 0 :(得分:10)
尝试:
groups = scenes.inject({}) do |new_hash, scene| new_hash[scene.number] = [] if new_hash[scene.number].nil? new_hash[scene.number] << scene new_hash end
Ruby将传递给inject()的空哈希值设置为new_hash。当块结束时,返回值用于在下一次初始化new_hash,即new_hash继续累积块的结果。
在你的原始代码中,你没有返回哈希值,但是一个数组(new_hash [scene.number]是一个数组)并且下一个循环通过Ruby抱怨,因为new_hash [scene.number]试图对数组进行查找带有字符串值,因此出现错误。
答案 1 :(得分:6)
Z.E.D.是的。有关inject
的详细解释,请参阅Jay Fields' Thoughts: Ruby: inject。
如图所示,您的块返回一个数组。因此new_hash
中的|new_hash, scene|
最终成为该数组。当Ruby尝试查找数组索引'one'时,它会抛出错误,因为'one'是String而不是Integer。
您需要做的就是返回new_hash
作为Z.E.D.显示,你会得到这样的东西:
{
"two" => [
#<Scene:0x101836470 @number="two">
],
"one" => [
#<Scene:0x101836510 @number="one">,
#<Scene:0x1018364c0 @number="one">,
#<Scene:0x101836420 @number="one">
]
}
答案 2 :(得分:2)
为什么不使用group_by,这可能正是你想要实现的目标?
groups = scenes.group_by(&:number)
# => {"two"=>[#<Scene:0xb728ade0 @number="two">],
# "one"=>
# [#<Scene:0xb728ae30 @number="one">,
# #<Scene:0xb728ae08 @number="one">,
# #<Scene:0xb728ada4 @number="one">]}
inject
是折叠操作,并不完全符合您的要求。至少以这种方式使用是很麻烦的。如果您想在合并或分组期间应用某种算法,那么带有块的merge
可能是合适的。
答案 3 :(得分:0)
另外,要解释'Ruby如何知道此变量的类型'以及它为什么尝试'将String转换为整数',您可能需要修改:Ruby variables and dynamic typing。
答案 4 :(得分:0)
我知道这个问题已经接受了答案,但我不禁发表答案。
groups = scenes.inject({}) { |nh, s| nh.tap {|h| (h[s.number] ||= []) << s } }