我有下面的代码,这给我标记的行带来了问题。
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]
一切如预期。我的第一个版本的台词是什么导致了这种意想不到的行为?
字符串#<lt;方法修改对其进行调用的原始对象。这是word
变量引用的对象对CCD_ 2数组中的字符串之一的另一个引用。你可以看到这个代码效果:
a = 'Hello'
b = a << ' ' << 'World'
puts a.__id__
puts b.__id__
因此,当您在迭代器的一次遍历中使用该方法时,它会影响接下来的传球也是如此。
另一方面,String#+方法创建一个新的String对象来保存组合字符串。使用此方法,遍历迭代器时没有对其他传球的影响。
key=单词<lt;"<lt;arr[index+1]
问题是String#<lt;执行就地操作,以便下次使用字符串时对其进行修改。另一方面,String#+返回一个新副本。
你已经被一个强制性的副作用所折磨(这并不罕见,因为副作用是bug的巨大来源。除非有非常令人信服的性能原因,否则函数方法会产生更好的代码)。例如,这就是使用Facets:中的each_cons
和map_by
编写它的方式
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