Ruby 哈希默认值行为
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16159370/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Ruby hash default value behavior
提问by Jake Sellers
I'm going through Ruby Koans, and I hit #41 which I believe is this:
我正在浏览 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
It could not understand the behavior so I Googled it and found Strange ruby behavior when using Hash default value, e.g. Hash.new([])that answered the question nicely.
它无法理解行为,所以我在谷歌上搜索并发现使用 Hash 默认值时奇怪的 ruby 行为,例如 Hash.new([])很好地回答了这个问题。
So I understand how that works, my question is, why does a default value such as an integer that gets incremented not get changed during use? For example:
所以我理解它是如何工作的,我的问题是,为什么在使用过程中不会更改默认值(例如递增的整数)?例如:
puts "Text please: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }
This will take user input and count the number of times each word is used, it works because the default value of 0 is always used.
这将接受用户输入并计算每个单词的使用次数,它之所以有效是因为始终使用默认值 0。
I have a feeling it has to do with the <<operator but I'd love an explanation.
我有一种感觉,这与<<运营商有关,但我希望得到解释。
回答by J?rg W Mittag
The other answers seem to indicate that the difference in behavior is due to Integers being immutable and Arrays being mutable. But that is misleading. The difference is not that the creatorof Ruby decided to make one immutable and the other mutable. The difference is that you, the programmerdecided to mutate one but not the other.
其他答案似乎表明行为的差异是由于Integers 是不可变的,而Arrays 是可变的。但这是误导。区别不在于Ruby的创建者决定使一个不可变,另一个可变。不同之处在于你,程序员决定变异一个而不是另一个。
The question is not whether Arrays are mutable, the question is whether you mutateit.
问题不在于Arrays 是否可变,而是您是否对其进行了变异。
You can get both the behaviors you see above, just by using Arrays. Observe:
您可以通过使用Arrays来获得上面看到的两种行为。观察:
One default Arraywith mutation
一种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!
One default Arraywithout mutation
一种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.
A new, different Arrayevery time with mutation
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 => [] }
回答by Hauleth
It is because Arrayin Ruby is mutable object, so you can change it internal state, but Fixnumisn't mutable. So when you increment value using +=internally it get that (assume that iis our reference to Fixnumobject):
这是因为Array在 Ruby 中是可变对象,所以你可以改变它的内部状态,但Fixnum不是可变的。因此,当您在+=内部使用递增值时,它会得到(假设这i是我们对Fixnum对象的引用):
- get object referenced by
i - get it internal value (lets name it
raw_tmp) - create new object that internal value is
raw_tmp + 1 - assign reference to created object to
i
- 获取被引用的对象
i - 获取它的内部值(让我们命名
raw_tmp) - 创建内部值为的新对象
raw_tmp + 1 - 将创建的对象的引用分配给
i
So as you can see, we created new object, and ireference now to something different than at the beginning.
如您所见,我们创建了新对象,并且i现在引用了与开始时不同的内容。
In the other hand, when we use Array#<<it works that way:
另一方面,当我们使用Array#<<它时,它是这样工作的:
- get object referenced by
arr - to it's internal state append given element
- 获取被引用的对象
arr - 给它的内部状态附加给定的元素
So as you can see it is much simpler, but it can cause some bugs. One of them you have in your question, another one is thread race when booth are trying simultaneously append 2 or more elements. Sometimes you can end with only some of them and with thrashes in memory, when you use +=on arrays too, you will get rid of both of these problems (or at least minimise impact).
如您所见,它要简单得多,但它可能会导致一些错误。其中一个是您的问题,另一个是当 Boot 尝试同时追加 2 个或更多元素时的线程竞争。有时,您可以仅以其中的一部分结束,并且内存中会出现颠簸,当您+=也在数组上使用时,您将摆脱这两个问题(或至少将影响降至最低)。
回答by user2398029
From the doc, setting a default value has the following behaviour:
从doc,设置默认值具有以下行为:
Returns the default value, the value that would be returned by hsh if key did not exist in hsh. See also Hash::new and Hash#default=.
返回默认值,如果 key 在 hsh 中不存在,hsh 将返回的值。另请参阅 Hash::new 和 Hash#default=。
Therefore, every time frequencies[word]is not set, the value for that individual key is set to 0.
因此,每次frequencies[word]未设置时,该单个键的值都设置为 0。
The reason for the discrepancy between the two code blocks is that arrays are mutable in Ruby, while integers are not.
两个代码块之间存在差异的原因是数组在 Ruby 中是可变的,而整数则不是。

