Ruby散列默认值行为

时间:2013-04-23 01:25:04

标签: ruby hash

我正在浏览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

它无法理解行为所以我用Google搜索并发现Strange ruby behavior when using Hash default value, e.g. Hash.new([])很好地回答了这个问题。

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

puts "Text please: "
text = gets.chomp

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

这将获取用户输入并计算每个单词的使用次数,它的工作原理是因为始终使用默认值0。

我觉得它与<<运算符有关,但我喜欢解释。

3 个答案:

答案 0 :(得分:105)

其他答案似乎表明行为的差异是由于Integer是不可变的,Array是可变的。但那是误导。不同之处并不在于Ruby的 creator 决定使一个不可变而另一个是可变的。区别在于程序员决定改变一个而不是另一个。

问题不在于Array是否可变,问题是你是否变异

只需使用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 => [] }

答案 1 :(得分:3)

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

  1. 获取i
  2. 引用的对象
  3. 获取内部值(让我们将其命名为raw_tmp
  4. 创建内部值为raw_tmp + 1
  5. 的新对象
  6. 将对已创建对象的引用分配给i
  7. 正如您所看到的,我们创建了新对象,i现在引用了与开头不同的内容。

    另一方面,当我们使用Array#<<时,它的工作方式如下:

    1. 获取arr
    2. 引用的对象
    3. 到它的内部状态追加给定元素
    4. 因为你可以看到它更简单,但它可能会导致一些错误。其中一个是你的问题,另一个是线程竞赛,当摊位试图同时附加2个或更多元素。有时你只能用它们中的一些和内存中的th th结束,当你在数组上使用+=时,你将摆脱这两个问题(或至少减少影响)。

答案 2 :(得分:1)

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

  

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

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

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