Подтвердить что ты не робот

Поведение значения по умолчанию для хеша 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.

У меня такое чувство, что оно связано с оператором <<, но мне бы хотелось объяснить.

4b9b3361

Ответ 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, а целые - нет.