Поведение значения по умолчанию для хеша Ruby
Я иду через 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
Он не мог понять поведение, поэтому я искал его и нашел странное рубиновое поведение при использовании значения по умолчанию Hash, например. Hash.new([]), который хорошо ответил на вопрос.
Итак, я понимаю, как это работает, мой вопрос, почему значение по умолчанию, такое как целое число, которое увеличивается, не изменяется во время использования? Например:
puts "Text please: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }
Это будет принимать пользовательский ввод и подсчитывать количество раз, когда каждое слово используется, оно работает, потому что всегда используется значение по умолчанию 0.
У меня такое чувство, что оно связано с оператором <<
, но мне бы хотелось объяснить.
Ответы
Ответ 1
Другие ответы, похоже, указывают на то, что разница в поведении обусловлена тем, что Integer
является неизменяемым и Array
является изменяемым. Но это вводит в заблуждение. Разница заключается не в том, что создатель Ruby решил сделать одну неизменяемую, а другую изменчивой. Разница в том, что вы, программист решили мутировать один, но не другой.
Вопрос не в том, является ли 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 => [] }
Ответ 2
Это потому, что Array
в Ruby является изменяемым объектом, поэтому вы можете изменить его внутреннее состояние, но Fixnum
не изменяет. Поэтому, когда вы увеличиваете значение с помощью +=
внутри, получаем это (предположим, что i
является нашей ссылкой на объект Fixnum
):
- получить объект, на который ссылается
i
- получить внутреннее значение (давайте назовите его
raw_tmp
)
- создать новый объект, внутреннее значение которого
raw_tmp + 1
- назначить ссылку на созданный объект на
i
Итак, как вы можете видеть, мы создали новый объект, а i
ссылаемся на нечто иное, чем на начало.
С другой стороны, когда мы используем Array#<<
, он работает именно так:
- получить объект, на который ссылается
arr
- к нему внутреннее состояние добавляет данный элемент
Итак, как вы можете видеть, это намного проще, но это может вызвать некоторые ошибки. Один из них у вас в вашем вопросе, другой - нить, когда стенд пытается одновременно добавить 2 или более элементов. Иногда вы можете завершить только некоторые из них и с трэшами в памяти, когда вы также используете +=
на массивах, вы избавитесь от обеих этих проблем (или, по крайней мере, минимизируете воздействие).
Ответ 3
Из doc установка значения по умолчанию имеет следующее поведение:
Возвращает значение по умолчанию, значение, которое будет возвращено hsh, если ключ не существовал в hsh. См. Также Hash:: new и Hash # default =.
Следовательно, каждый раз, когда frequencies[word]
не задано, значение для этого индивидуального ключа устанавливается равным 0.
Причиной расхождения между двумя кодовыми блоками является то, что массивы изменяются в Ruby, а целые - нет.