Modifying the default hash value [duplicate]

2019-01-26 09:27发布

问题:

This question already has an answer here:

  • Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([]) 4 answers

Ruby lets you define default values for hashes:

h=Hash.new(['alright'])
h['meh'] # => ["alright"]

Assignment of a value shows up when displaying the hash, but a modified default does not. Where's 'bad'?

h['good']=['fine','dandy']
h['bad'].push('unhappy')
h # => {"good"=>["fine", "dandy"]}

'bad' shows up if we explicitly ask.

h['bad'] # => ["alright", "unhappy"]

Why does the modified default value not show up when displaying the hash?

回答1:

Hash's default value doesn't work like you're expecting it to. When you say h[k], the process goes like this:

  1. If we have a k key, return its value.
  2. If we have a default value for the Hash, return that default value.
  3. If we have a block for providing default values, execute the block and return its return value.

Note that (2) and (3) say nothing at all about inserting k into the Hash. The default value essentially turns h[k] into this:

h.has_key?(k) ? h[k] : the_default_value

So simply accessing a non-existant key and getting the default value back won't add the missing key to the Hash.

Furthermore, anything of the form:

Hash.new([ ... ])
# or
Hash.new({ ... })

is almost always a mistake as you'll be sharing exactly the same default Array or Hash for for all default values. For example, if you do this:

h = Hash.new(['a'])
h[:k].push('b')

Then h[:i], h[:j], ... will all return ['a', 'b'] and that's rarely what you want.

I think you're looking for the block form of the default value:

h = Hash.new { |h, k| h[k] = [ 'alright' ] }

That will do two things:

  1. Accessing a non-existent key will add that key to the Hash and it will have the provided Array as its value.
  2. All of the default values will be distinct objects so altering one will not alter the rest.


回答2:

What's happened is that you have modified the default value of the hash, by pushing 'unhappy' onto h['bad']. What you haven't done is actually added 'bad' to the hash, which is why it doesn't show up when you inspect h.

After all the code you supplied, I tried this:

>> p h['bleh']
=> ["allright", "unhappy"]

Which certainly suggests to me that the default value has been changed. In answer to your question 'Why does the modified default not show up when displaying the hash?', you would have to add an element to it, rather than just accessing it:

>> h['bleh']  # Doesn't add 'bleh' to the hash
>> p h
=> {"good"=>["fine", "dandy"]} # See, no extra values

>> h['bleh'] = h.default  # Does add a new key with the default value
>> p h
=> {"good"=>["fine", "dandy"], "bleh"=>["allright", "unhappy"]}