Ruby метод Array # << не обновляет массив в хэше - PullRequest
14 голосов
/ 31 марта 2010

Вдохновленный Как я могу собрать хеш с массивами? Интересно, в чем причина 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#+ создает новый экземпляр?

Ответы [ 3 ]

33 голосов
/ 31 марта 2010

Если вы создаете 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']

Что здесь происходит? Во-первых, оператор присваивается в

h[0] = h[0] + ['a']

Теперь h[0] на правой стороне оценивается. И что это возвращает? Мы уже говорили об этом: h[0] не существует, поэтому блок запускается, блок возвращает пустой массив. Опять же, теперь это совершенно новый, третий пустой массив. Этот пустой массив получает сообщение + с аргументом ['a'], что заставляет его возвращать еще другой новый массив, который является массивом ['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], вы получаете другой массив.

3 голосов
/ 31 марта 2010

Проблема в вашем коде состоит в том, что h[0] << 'a' создает новый массив и выдает его при индексировании с помощью h[0], но не сохраняет измененный массив где-либо после << 'a', потому что нет присваивания.

Между тем h[0] += ['a'] работает, потому что это эквивалентно h[0] = h[0] + ['a']. Это назначение ([]=), которое имеет значение.

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

0 голосов
/ 31 марта 2010
h = Hash.new{ |a,b| a[b] = Array.new }
h[0] << "hello world"
#=> ["hello world"]
h[0]
#=> ["hello world"]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...