Вдохновленный тем, Как я могу упорядочить хеш с массивами? Интересно, какова причина это Array#<<
не будет работать правильно в следующем коде:
h = Hash.new{Array.new}
#=> {}
h[0]
#=> []
h[0] << 'a'
#=> ["a"]
h[0]
#=> [] # why?!
h[0] += ['a']
#=> ["a"]
h[0]
#=> ["a"] # as expected
Это имеет отношение к факту это <<
изменяет оперативный массив, в то время как Array#+
создает новый экземпляр?
Если вы создаете Hash
, используя блочную форму Hash.new
, блок запускается каждый раз, когда вы пытаетесь получить доступ к элементу, который на самом деле не существует. Итак, давайте просто посмотрим, что происходит:
h = Hash.new { [] }
h[0] << 'a'
Первое, что здесь вычисляется, - это выражение
h[0]
Что происходит, когда оно вычисляется? Итак, блок запускается:
[]
Это не очень интересно: блок просто создает пустой массив и возвращает его. Больше он ничего не делает. В частности, он никак не меняет h
: h
по-прежнему пуст.
Затем сообщение <<
с одним аргументом 'a'
отправляется в результат h [0]
, который является результатом блока , который представляет собой просто пустой массив:
[] << 'a'
Что это делает? Он добавляет элемент 'a'
в пустой массив, но поскольку массив фактически не назначается какой-либо переменной, он немедленно удаляется сборщиком мусора.
Теперь, если вы снова оцените h [0]
:
h[0] # => []
h
все еще пусто, так как ему ничего не было назначено, поэтому ключ ] 0
по-прежнему не существует, что означает, что блок снова запускается , что означает, что он снова возвращает пустой массив (но обратите внимание, что это совершенно новый, другой пустой массив).
h[0] += ['a']
Что здесь происходит? Сначала оператор assign сбрасывается с
h[0] = h[0] + ['a']
. Теперь вычисляется h [0]
на правой стороне . И что это возвращает? Мы уже рассмотрели это: h [0]
не существует, поэтому блок запускается, блок возвращает пустой массив. Опять же, это совершенно новый, третий пустой массив. Этот пустой массив получает сообщение +
с аргументом ['a']
, что приводит к возврату еще другого нового массива, который является массивом ] ['а']
. Затем этот массив присваивается h [0]
.
Наконец, на этом этапе:
h[0] # => ['a']
Теперь у вас есть , наконец, действительно что-то помещают в h [0]
, так что, очевидно, вы получаете то, что вставили.
Итак, отвечая на вопрос, который у вас, вероятно, был, почему бы вам не выложить то, что вы вставили? Вы ничего не вставляли в первую очередь!
Если вы действительно хотите назначить хешу внутри блока, вы должны хорошо назначить хэш внутри блока:
h = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] }
h[0] << 'a'
h[0] # => ['a']
На самом деле довольно легко увидеть, что происходит в вашем пример кода, если вы посмотрите на идентификаторы задействованных объектов. Затем вы можете видеть, что каждый раз, когда вы вызываете h [0]
, вы получаете другой массив .
h = Hash.new{ |a,b| a[b] = Array.new }
h[0] << "hello world"
#=> ["hello world"]
h[0]
#=> ["hello world"]
Проблема в вашем коде заключается в том, что h [0] << ' a '
создает новый массив и выдает его при индексировании с помощью h [0]
, но не сохраняет измененный массив нигде после <<' a '
потому что нет задания.
Между тем h [0] + = ['a']
работает, потому что он эквивалентен h [0] = h [0] + ['a']
. Все дело в присваивании ([] =
).
Первый случай может показаться запутанным, но он полезен, когда вы просто хотите получить какой-то неизменный элемент по умолчанию из хэша, когда ключ не найден. В противном случае вы можете заполнить хэш большим количеством неиспользуемых значений, просто проиндексировав его.