2016-12-11 2 views
3

Учитывая два двумерный массив в Ruby:Преобразование Рубин массив элементов Hash графов с индексами

[ [1, 1, 1], 
    [1, 1], 
    [1, 1, 1, 1], 
    [1, 1] 
] 

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

{ 2 => [1, 3], 3 => [0], 4 => [2] } 

Как я могу кратко выразить это функционально в Ruby? Я пытаюсь что-то похожее на Hash.new([]).tap { |h| array.each_with_index { |a, i| h[a.length] << i } }, но полученный Хэш пуст.

ответ

5

Есть две проблемы с вашим кодом. Во-первых, когда 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.

+0

Ах, вот как вы приковать его. Сначала я пытался «arr.with_object» и не смог. – ybakos

+2

Очень полезно '# with_object', который позволяет избежать запаха кода, который я использовал в своем ответе, объявляя хэш из блока. Еще раз спасибо, Кэри. Это привилегия, имеющая возможность учиться у такого мастера, как ты. –

+0

@ybakos 'with_object' работает только с объектами Enumerator. Или вы можете сказать, что класс Array не имеет метода 'with_object'. –

3
arry = [ [1, 1, 1], 
     [1, 1], 
     [1, 1, 1, 1], 
     [1, 1] 
     ] 

h = {} 
arry.each_with_index do |el,i| 
    c = el.count 
    h.has_key?(c) ? h[c] << i : h[c] = [i] 
end 

p h 

Это даст вам

{3=>[0], 2=>[1, 3], 4=>[2]} 
Смежные вопросы