首页 > 解决方案 > 为什么我在 ruby​​ 中收到此错误:find_word_lengths 中的块':未定义方法 `[]=' for 3:Integer (NoMethodError)

问题描述

以下代码返回此错误:

find_word_lengths 中的块:未定义的方法 `[]=' for 3:Integer (NoMethodError)

animals = ['cat', 'horse', 'rabbit', 'deer']

def find_word_lengths(word_list)
  word_list.reduce(Hash.new()) do |result, animal|
    result[animal] = animal.length
  end 
end

puts find_word_lengths(animals)

标签: ruby

解决方案


块的返回值是下一次迭代的累加器值。这就是折叠的工作原理。

Ruby 中的赋值计算在右侧。因此,在 的第一次迭代中reduce,块的计算结果为3(的长度'cat')。这意味着在 , is 的第二次迭代中,reduce您实际上是在运行result3

3['horse'] = 5
# which is equivalent to
3.[]=('horse', 5)

这就是为什么您收到错误消息,即Integer 3不响应消息的原因[]=

因此,您需要确保您的块始终返回您希望在下一次迭代中用于累加器的值。像这样的东西:

word_list.reduce(Hash.new()) do |result, animal|
  result.tap {|result| result[animal] = animal.length }
end

这将是显而易见的解决方案,尽管有些作弊。

word_list.reduce(Hash.new()) do |result, animal|
  result.merge(animal => animal.length)
end

会更加地道。

但是,当您想要折叠成可变对象时,使用Enumerable#each_with_object而不是Enumerable#reduce. each_with_object忽略块的结果,并且每次都简单地传递相同的对象。请注意,有点令人困惑的是,块参数的顺序each_with_objectreduce.

word_list.each_with_object(Hash.new()) do |animal, result|
  result[animal] = animal.length
end

但我想最惯用的解决方案是这样的:

word_list.map {|word| [word, word.length] }.to_h

顺便说一句,在 Ruby 中,如果您不传递任何参数,则省略参数列表的括号是惯用的,因此Hash.new()应该Hash.new改为。比惯用语更重要的是保持一致——令人困惑的是,你省略了 的括号animal.length,但没有Hash.new

更惯用的是,您将使用Hash文字符号而不是Hash::new方法,即您应该使用{}而不是Hash.new.


推荐阅读