48

我正在浏览 Ruby Koans,我点击了 #41,我相信这是:

def test_default_value_is_the_same_object
  hash = Hash.new([])

  hash[:one] << "uno"
  hash[:two] << "dos"

  assert_equal ["uno","dos"], hash[:one]
  assert_equal ["uno","dos"], hash[:two]
  assert_equal ["uno","dos"], hash[:three]

  assert_equal true, hash[:one].object_id == hash[:two].object_id
end

它无法理解这种行为,所以我用谷歌搜索它并发现使用 Hash 默认值时奇怪的 ruby​​ 行为,例如 Hash.new([])很好地回答了这个问题。

所以我理解它是如何工作的,我的问题是,为什么在使用过程中不会改变一个默认值,比如一个递增的整数?例如:

puts "Text please: "
text = gets.chomp

words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }

这将接受用户输入并计算每个单词的使用次数,它之所以有效,是因为始终使用默认值 0。

我觉得这与<<操作员有关,但我希望得到解释。

4

3 回答 3

124

其他答案似乎表明行为的差异是由于Integers 是不可变的而Arrays 是可变的。但这是误导。不同之处不在于 Ruby 的创建者决定让一个不可变而另一个可变。不同之处在于程序员决定改变一个而不改变另一个。

问题不在于Arrays 是否可变,而在于你是否改变了它。

Array您可以通过使用s来获得上面看到的两种行为。观察:

Array一种带有突变的默认值

hsh = Hash.new([])

hsh[:one] << 'one'
hsh[:two] << 'two'

hsh[:nonexistent]
# => ['one', 'two']
# Because we mutated the default value, nonexistent keys return the changed value

hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!

Array一种没有突变的默认值

hsh = Hash.new([])

hsh[:one] += ['one']
hsh[:two] += ['two']
# This is syntactic sugar for hsh[:two] = hsh[:two] + ['two']

hsh[:nonexistant]
# => []
# We didn't mutate the default value, it is still an empty array

hsh
# => { :one => ['one'], :two => ['two'] }
# This time, we *did* mutate the hash.

一个新的,Array每次都不同的突变

hsh = Hash.new { [] }
# This time, instead of a default *value*, we use a default *block*

hsh[:one] << 'one'
hsh[:two] << 'two'

hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.

hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!


hsh = Hash.new {|hsh, key| hsh[key] = [] }
# This time, instead of a default *value*, we use a default *block*
# And the block not only *returns* the default value, it also *assigns* it

hsh[:one] << 'one'
hsh[:two] << 'two'

hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.

hsh
# => { :one => ['one'], :two => ['two'], :nonexistent => [] }
于 2013-04-23T10:17:01.073 回答
4

这是因为Array在 Ruby 中是可变对象,所以你可以改变它的内部状态,但Fixnum不是可变的。因此,当您使用+=内部增加值时,它会得到(假设这i是我们对Fixnum对象的引用):

  1. 获取引用的对象i
  2. 获取它的内部价值(让我们命名它raw_tmp
  3. 创建内部值是的新对象raw_tmp + 1
  4. 将创建对象的引用分配给i

如您所见,我们创建了新对象,并且i现在引用了与开始时不同的东西。

另一方面,当我们使用Array#<<它时,它是这样工作的:

  1. 获取引用的对象arr
  2. 到它的内部状态附加给定元素

所以你可以看到它要简单得多,但它可能会导致一些错误。其中一个是您的问题,另一个是当展位尝试同时附加 2 个或更多元素时的线程竞赛。有时你可以只用其中的一些和内存中的颠簸结束,当你+=也在数组上使用时,你将摆脱这两个问题(或至少最小化影响)。

于 2013-04-23T01:35:59.060 回答
1

doc,设置默认值具有以下行为:

返回默认值,如果密钥在 hsh 中不存在,则 hsh 将返回的值。另请参阅 Hash::new 和 Hash#default=。

因此,每次frequencies[word]未设置时,该单个键的值都设置为 0。

两个代码块之间存在差异的原因是数组在 Ruby 中是可变的,而整数则不是。

于 2013-04-23T01:31:08.910 回答