Есть две проблемы с вашим кодом. Во-первых, когда h
пуста, и вы пишете, скажем, h[2] << 1
, так как h
не имеет ключа 2
, h[2]
возвращает значение по умолчанию, так что это выражение становится [] << 1 #=> [1]
, но [1]
не привязан к хэша, так что нет ключа и значения добавлены.
Вам необходимо написать h[2] = h[2] << 1
. Если вы это сделаете, ваш код вернет h #=> {3=>[0, 1, 2, 3], 2=>[0, 1, 2, 3], 4=>[0, 1, 2, 3]}
. К сожалению, это по-прежнему неверно, что приводит к второй проблеме с вашим кодом: вы не указали правильно заданное значение по умолчанию для хэша.
Прежде всего заметим, что
h[3].object_id
#=> 70113420279440
h[2].object_id
#=> 70113420279440
h[4].object_id
#=> 70113420279440
Аха, все три значения и тот же объект! new
аргументы []
не возвращается h[k]
когда h
не имеет ключа k
. Проблема в том, что это тот же массив возвращается для всех ключей k
, добавленных в хэш, поэтому вы добавляете пару ключ-значение в пустой массив для первого нового ключа, а затем добавляете вторую пару ключ-значение в что такой же массив для следующего нового ключа и так далее. См. Ниже, как определить хэш.
С этими двумя изменениями ваш код работает нормально, но я бы предложил написать его следующим образом.
arr = [ [1, 1, 1], [1, 1], [1, 1, 1, 1], [1, 1] ]
arr.each_with_index.with_object(Hash.new {|h,k| h[k]=[]}) { |(a,i),h|
h[a.size] << i }
#=> {3=>[0], 2=>[1, 3], 4=>[2]}
, которые используют форму Hash::new, которая использует блок для вычисления значения хэша по умолчанию (то есть значение, возвращаемое h[k]
когда хэш h
не имеет ключ k
),
или
arr.each_with_index.with_object({}) { |(a,i),h| (h[a.size] ||= []) << i }
#=> {3=>[0], 2=>[1, 3], 4=>[2]}
оба из которых являются эффективно следующее:
h = {}
arr.each_with_index do |a,i|
sz = a.size
h[sz] = [] unless h.key?(sz)
h[a.size] << i
end
h #=> {3=>[0], 2=>[1, 3], 4=>[2]}
Другим способом является использование Enumerable#group_by, группировка по размеру массива, после подбора индекса для каждого внутреннего массива.
h = arr.each_with_index.group_by { |a,i| a.size }
#=> {3=>[[[1, 1, 1], 0]],
# 2=>[[[1, 1], 1], [[1, 1], 3]],
# 4=>[[[1, 1, 1, 1], 2]]}
h.each_key { |k| h[k] = h[k].map(&:last) }
#=> {3=>[0], 2=>[1, 3], 4=>[2]}
1 Выражение h[2] = h[2] << 1
использует методы Hash#[]= и Hash#[], поэтому h[2]
слева от =
не возвращает значение по умолчанию. Это выражение можно альтернативно написать h[2] ||= [] << 1
.
Ах, вот как вы приковать его. Сначала я пытался «arr.with_object» и не смог. – ybakos
Очень полезно '# with_object', который позволяет избежать запаха кода, который я использовал в своем ответе, объявляя хэш из блока. Еще раз спасибо, Кэри. Это привилегия, имеющая возможность учиться у такого мастера, как ты. –
@ybakos 'with_object' работает только с объектами Enumerator. Или вы можете сказать, что класс Array не имеет метода 'with_object'. –