Во-первых, обратите внимание, что это поведение применяется к любому значению по умолчанию, которое впоследствии изменяется (например, хешам и строкам), а не только к массивам.
TL; DR : используйте, Hash.new { |h, k| h[k] = [] }
если хотите наиболее идиоматическое решение, и вам все равно, почему.
Что не работает
Почему Hash.new([])
не работает
Давайте более подробно рассмотрим, почему Hash.new([])
не работает:
h = Hash.new([])
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["a", "b"]
h[1] #=> ["a", "b"]
h[0].object_id == h[1].object_id #=> true
h #=> {}
Мы можем видеть, что наш объект по умолчанию повторно используется и видоизменяется (это потому, что он передается как единственное и неповторимое значение по умолчанию, хэш не имеет возможности получить новое, новое значение по умолчанию), но почему нет ключей или значений в массиве, несмотря на то, что h[1]
все еще дает нам значение? Вот подсказка:
h[42] #=> ["a", "b"]
Массив, возвращаемый каждым []
вызовом, является просто значением по умолчанию, которое мы все это время изменяли, поэтому теперь он содержит наши новые значения. Поскольку <<
не присваивается хешу (в Ruby никогда не может быть присваивания без =
подарка † ), мы никогда ничего не помещали в наш фактический хэш. Вместо этого мы должны использовать <<=
( <<
как +=
есть +
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
Это то же самое, что:
h[2] = (h[2] << 'c')
Почему Hash.new { [] }
не работает
Использование Hash.new { [] }
решает проблему повторного использования и изменения исходного значения по умолчанию (поскольку данный блок вызывается каждый раз, возвращая новый массив), но не проблему присваивания:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
Что работает
Путь задания
Если мы будем помнить всегда использовать <<=
, то Hash.new { [] }
это жизнеспособное решение, но это немного странно и не идиоматические (я никогда не видел <<=
использоваться в дикой природе). Он также подвержен незначительным ошибкам, если <<
используется непреднамеренно.
Изменчивый путь
Документация дляHash.new
государств (курсив мой собственный):
Если блок указан, он будет вызываться с хеш-объектом и ключом и должен вернуть значение по умолчанию. Ответственность за сохранение значения в хэше лежит на блоке, если это необходимо .
Поэтому мы должны сохранить значение по умолчанию в хэше внутри блока, если мы хотим использовать <<
вместо <<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
Это эффективно перемещает назначение из наших индивидуальных вызовов (которые будут использоваться <<=
) в переданный блок Hash.new
, снимая бремя неожиданного поведения при использовании <<
.
Обратите внимание, что есть одно функциональное отличие между этим методом и другими: этот способ присваивает значение по умолчанию при чтении (поскольку присвоение всегда происходит внутри блока). Например:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
Неизменный путь
Вам может быть интересно, почему Hash.new([])
не работает, а Hash.new(0)
работает нормально. Ключ в том, что числовые значения в Ruby неизменяемы, поэтому мы, естественно, никогда не будем изменять их на месте. Если бы мы относились к нашему значению по умолчанию как к неизменяемому, мы Hash.new([])
тоже могли бы использовать :
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
Однако обратите внимание на это ([].freeze + [].freeze).frozen? == false
. Итак, если вы хотите гарантировать, что неизменность сохраняется повсюду, вы должны позаботиться о повторном замораживании нового объекта.
Вывод
Из всех способов я лично предпочитаю «неизменный путь» - неизменность обычно делает рассуждения о вещах намного проще. В конце концов, это единственный метод, у которого нет возможности скрытого или незаметного неожиданного поведения. Однако наиболее распространенный и идиоматический способ - это «изменчивый путь».
И наконец, такое поведение значений хэша по умолчанию отмечено в Ruby Koans .
† Это не совсем так, такие методы, как instance_variable_set
обход этого, но они должны существовать для метапрограммирования, поскольку l-значение in =
не может быть динамическим.
{ [] }
with<<=
меньше всего сюрпризов, если бы не тот факт, что случайное забывание=
могло бы привести к очень запутанному сеансу отладки.Вы указываете, что значение по умолчанию для хэша является ссылкой на этот конкретный (изначально пустой) массив.
Я думаю, ты хочешь:
Это устанавливает значение по умолчанию для каждого ключа в новый массив.
источник
Array
экземпляры при каждом вызове. А именно:h = Hash.new { |hash, key| hash[key] = []; puts hash[key].object_id }; h[1] # => 16348490; h[2] # => 16346570
. Также: если вы используете версию блока, которая устанавливает значение ({|hash,key| hash[key] = []}
), а не ту, которая просто генерирует значение ({ [] }
), тогда вам нужно только<<
, а не<<=
при добавлении элементов.Оператор,
+=
примененный к этим хешам, работает должным образом.Это может быть потому, что
foo[bar]+=baz
это синтаксический сахар,foo[bar]=foo[bar]+baz
когдаfoo[bar]
при оценке справа=
он возвращает объект значения по умолчанию, и+
оператор не изменит его. Левая часть - синтаксический сахар для[]=
метода, который не изменяет значение по умолчанию .Обратите внимание , что это не относится к ,
foo[bar]<<=baz
как это будет эквивалентноfoo[bar]=foo[bar]<<baz
и<<
будет изменить значение по умолчанию .Кроме того, я не нашел разницы между
Hash.new{[]}
иHash.new{|hash, key| hash[key]=[];}
. Хотя бы на рубине 2.1.2.источник
Hash.new{[]}
то же самое, что иHash.new([])
у меня, с отсутствием ожидаемого<<
поведения (хотя, конечно,Hash.new{|hash, key| hash[key]=[];}
работает). Странные мелочи, ломающие все: /Когда пишешь,
вы передаете ссылку на массив по умолчанию всем элементам в хеше. из-за этого все элементы в хэше относятся к одному и тому же массиву.
если вы хотите, чтобы каждый элемент в хэше ссылался на отдельный массив, вы должны использовать
для более подробной информации о том, как это работает в Ruby, просмотрите это: http://ruby-doc.org/core-2.2.0/Array.html#method-c-new
источник
Hash.new { [] }
это не работает. Подробности смотрите в моем ответе . Это также уже решение, предложенное в другом ответе.