【问题标题】:Ruby hash default value behaviorRuby 哈希默认值行为
【发布时间】:2013-04-16 01:53:15
【问题描述】:

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

我感觉它与 &lt;&lt; 运算符有关,但我希望得到解释。

【问题讨论】:

  • 我相信我看到 '
  • 你错了,我认为没有人曾经这样称呼它。根据谷歌的说法,唯一这样做的人是。第一个也是唯一相关的结果就是这个问题:google.ca/…
  • 可能与:: 混淆,有时称为范围解析运算符。
  • 不,我刚刚查了一下,我看过的其中一个 tuts 将其称为“铲子”,我记错了。我相信,正确的名称只是连接运算符,prolly 应该就这样了。
  • 它也不是连接运算符。它是按位左移运算符,也用作附加运算符(用于容器和流)。连接运算符是+

标签: ruby hash


【解决方案1】:

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

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

只需使用Arrays,您就可以获得上面看到的两种行为。观察:

一个默认的 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 => [] }

【讨论】:

  • 对于阅读此答案的其他人,请注意 :nonexistent 只是任何名称......它可能是 :foo:bar
  • 请注意,使用Hash.new {|hsh, key| hsh[key] = [] }时,每次都是一个新的Array实例,而如果是Hash.new([]),则每次没有key时都是完全相同的Array实例存在
【解决方案2】:

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

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

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

另一方面,当我们使用 Array#&lt;&lt; 时,它的工作方式是:

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

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

【讨论】:

  • 好的,感谢 Lukasz,我现在看到它被引用而不是像我想象的那样被复制。 :)
  • 做 1+=1 时没有创建新对象。 (查看整数的 object_id 以了解它们是如何工作的。)
  • @steenslag,我知道。这是无效的声明。
【解决方案3】:

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

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

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

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

【讨论】:

  • 是的,我对两个代码块中的行为之间的明显差异感兴趣。第一个,默认值被使用修改,第二个它似乎是不可变的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-31
  • 2014-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多