2012-11-11 4 views
2

Можно создать дубликат:
Ruby method Array#<< not updating the array in hash
Strange ruby behavior when using Hash.new([])Hash.new ([]) не ведет себя, как и ожидалось

Я делал Koans, который является большим, и, как я иду и я не нахожу никаких серьезных проблем, но я наткнулся на это и не могу сделать из этого никакого смысла:

def test_default_value_is_the_same_object 
     hash = Hash.new([]) 

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

     assert_equal ["uno", "dos"], hash[:one] # But I only put "uno" for this key! 
     assert_equal ["uno", "dos"], hash[:two] # But I only put "dos" for this key! 
     assert_equal ["uno", "dos"], hash[:three] # I didn't shove anything for :three! 

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

Все тесты проходят (я просто посмотрел на ошибку, которая помогла мне угадать правильные утверждения для написания).

Последнее утверждение, нормально, оба они не были инициализированы, поэтому их значения имеют одинаковый идентификатор объекта, поскольку оба они принимают значение по умолчанию.

Я не понимаю, почему значение по умолчанию было изменено, я даже не уверен, что это произошло.

Я попробовал это в IRB, думая, что, возможно, какое-то вмешательство в Hash/Array было сделано, чтобы сойти с ума, но я получаю тот же результат.

Я сначала подумал hash[:one] << "uno" будет означать hash стать { one: ["uno] }, но остается { }.
Хотя я предполагаю, что << вызывает только push и новые ключи добавляются только при использовании = знака

Пожалуйста, скажите мне, что я пропустил.

EDIT: Я использую рубин 1.9.3

+1

Существует только * один * массив: значение ('[]') оценивается * до того, как вызывается метод 'new'. Попробуйте «форму по умолчанию», которая берет блок (для создания массива * new * для реального времени каждый раз, когда блок получает/вызывается). Я уверен, что это дубликат. –

+0

Да, укусил это пару раз :) –

+0

@pst вы могли бы разработать? Я не уверен, что буду следовать. И я не нашел подобных вопросов в SO/Google/Duckduckgo, но я всегда могу пропустить вещи;) –

ответ

3

Когда вы используете аргумент по умолчанию для Hash, тот же объект используется для всех ключей, которые не были явно заданы. Это означает, что здесь используется только один массив, тот, который вы передали в Hash.new. Ниже приводятся доказательства этого.

>> h = Hash.new([]) 
=> {} 
>> h[:foo] << :bar 
=> [:bar] 
>> h[:bar] << :baz 
=> [:bar, :baz] 
>> h[:foo].object_id 
=> 2177177000 
>> h[:bar].object_id 
=> 2177177000 

Странно то, что, как вы обнаружили, если вы проверите хэш, вы обнаружите, что он пуст!Это происходит потому, что был изменен только объект по умолчанию, но еще не назначены ключи.

К счастью, есть еще один способ сделать значения по умолчанию для хэшей. Вы можете предоставить блок по умолчанию вместо:

>> h = Hash.new { |h,k| h[k] = [] } 
=> {} 
>> h[:foo] << :bar 
=> [:bar] 
>> h[:bar] << :baz 
=> [:baz] 
>> h[:foo].object_id 
=> 2176949560 
>> h[:bar].object_id 
=> 2176921940 

При использовании этого подхода, блок запускается на выполнение каждый раз, когда используются неприсвоенный ключ, и он предоставил сам хеш и ключ в качестве аргумента. Назначив значение по умолчанию в блоке, вы можете быть уверены, что новый объект будет создан для каждого отдельного ключа и что назначение произойдет автоматически. Это идиоматический способ создания «Hash of Arrays» в Ruby и, как правило, безопаснее использовать, чем подход по умолчанию.

При этом, если вы работаете с неизменяемыми значениями (например, номерами), выполнение чего-то вроде Hash.new(0) безопасно, так как вы только измените эти значения путем повторного назначения. Но поскольку я предпочитаю держать в голове меньше концепций, я в значительной степени использую блок-форму.

1

Когда вы

h = Hash.new(0) 
h[:foo] += 1 

вы непосредственно изменяющее h. h[:foo] += 1 - это то же самое, что и h[:foo] = h[:foo]+1. h[:foo] присваивается 0+1.

Когда вы

h = Hash.new([]) 
h[:foo] << :bar 

вы изменяющее h[:foo], который [], который является значением по умолчанию h, но не является значением для любого ключа h. После этого [] становится [:bar], значение по умолчанию h становится [:bar], но это не значение для h[:foo].

Смежные вопросы