0

我有以下代码导致我在我标记的行周围出现问题。

arr = 'I wish I may I wish I might'.split

dictionary = Hash.new

arr.each_with_index do |word, index|
  break if arr[index + 2] == nil

  key = word << " " << arr[index + 1] #This is the problem line 
  value = arr[index + 2]

  dictionary.merge!( { key => value } ) { |key, v1, v2| [v1] << v2 }
end
puts dictionary

运行此代码,我希望得到以下输出:

{"I wish"=>["I", "I"], "wish I"=>["may", "might"], "I may"=>"I", "may I"=>"wish"}

但是,我得到的是

{"I wish"=>["I may", "I"], "wish I"=>["may I", "might"], "I may"=>"I wish", "may I"=>"wish I"}

我发现如果我将问题行替换为

key = word + " " + arr[index + 1]

一切都按预期工作。我的第一个版本导致意外行为的原因是什么?

4

2 回答 2

1

键 = 单词 << " " << arr[索引 + 1]

问题是String#<<执行就地操作,因此下次使用该字符串时会对其进行修改。另一方面,String#+返回一个新副本。

您已经被命令式副作用所困扰(这并不罕见,因为副作用是错误的巨大来源。除非有非常令人信服的性能原因,否则函数式方法会产生更好的代码)。例如,这就是使用each_consmap_by从 Facets 编写它的方式:

words = 'I wish I may I wish I might'.split
dictionary = words.each_cons(3).map_by do |word1, word2, word3|
  ["#{word1} #{word2}", word3]
end
于 2013-08-09T15:48:20.103 回答
1

String#<<方法修改调用它的原始对象。这是您的word变量引用的对象,它只是对arr数组中的一个字符串的另一个引用。你可以通过代码看到这个效果:

 a = 'Hello'
 b = a << ' ' << 'World'
 puts a.__id__
 puts b.__id__

因此,当您一次通过迭代器使用该方法时,它也会影响以下通过。

另一方面,String#+方法创建一个新的 String 对象来保存组合的字符串。使用这种方法,通过迭代器的一个过程对其他过程没有影响。

于 2013-08-09T15:50:38.763 回答