Конвертировать массив из 2-х элементов в хеш, где дублирующиеся ключи добавляют дополнительные значения - PullRequest
32 голосов
/ 14 февраля 2012

Например

Учитывая массив:

array = [[:a,:b],[:a,:c],[:c,:b]]

Вернуть следующий хеш:

hash = { :a => [:b,:c] , :c => [:b] }

hash = Hash[array] перезаписывает предыдущие ассоциации, производя:

hash = { :a => :c , :c => :b }

Ответы [ 3 ]

75 голосов
/ 14 февраля 2012

Использование функциональных шагов ребенка:

irb:01.0> array = [[:a,:b],[:a,:c],[:c,:b]]
#=> [[:a, :b], [:a, :c], [:c, :b]]

irb:02.0> array.group_by(&:first)
#=> {:a=>[[:a, :b], [:a, :c]], :c=>[[:c, :b]]}

irb:03.0> array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] }
#=> [[:a, [:b, :c]], [:c, [:b]]]

irb:04.0> Hash[ array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } ]
#=> {:a=>[:b, :c], :c=>[:b]}

Использование императивного стиля программирования:

irb:10.0> h = Hash.new{ |h,k| h[k]=[] }
#=> {}

irb:11.0> array.each{ |k,v| h[k] << v }
#=> [[:a, :b], [:a, :c], [:c, :b]]

irb:12.0> h
#=> {:a=>[:b, :c], :c=>[:b]}

В качестве обязательного однострочного:

irb:13.0> h = Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
#=> {:a=>[:b, :c], :c=>[:b]}

Или используя любимый всем inject:

irb:14.0> array.inject(Hash.new{ |h,k| h[k]=[] }){ |h,(k,v)| h[k] << v; h }
#=> {:a=>[:b, :c], :c=>[:b]}

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

irb:17.0> hashes = array.map{ |pair| Hash[*pair] } # merge many mini hashes
#=> [{:a=>:b}, {:a=>:c}, {:c=>:b}]

irb:18.0> hashes.inject{ |h1,h2| h1.merge(h2){ |*a| a[1,2] } }
#=> {:a=>[:b, :c], :c=>:b}
29 голосов
/ 31 декабря 2013

РЕДАКТИРОВАТЬ: В Ruby 2.1+ вы можете использовать Array # to_h

pry(main)> [[:a,:b],[:a,:c],[:c,:b]].to_h
=> {:a=>:c, :c=>:b}

КОНЕЦ РЕДАКТИРОВАНИЯ

Открытый метод [] класса Hash принимает массив пар ключ-значение и возвращает хэш, в котором первый элемент массива является ключом, а второй - значением.

Последнее значение в паре ключ-значение будет фактическим значением при наличии дубликатов клавиш.

Hash[[[:a,:b],[:a,:c],[:c,:b]]]
    => {:a=>:c, :c=>:b}

Этот синтаксис действителен в 1.9.3+; Я не уверен насчет более ранних версий Ruby (это не действует в 1.8.7)

ref: http://www.ruby -doc.org / core-2.1.0 / Hash.html # method-c-5B-5D

Еще один интересный способ сделать это - использовать метод inject: (очевидно, что приведенный выше метод более краткий и рекомендуется для этой конкретной проблемы)

[ [:a, :b], [:a, :c], [:c, :b] ].inject({}) { |memo, obj| 
   memo[obj.first] = obj.last
   memo 
}

=> {:a=>:c, :c=>:b}

inject перебирает перечисляемый массив, в данном случае, начиная с введенного параметра, в данном случае - пустой хэш {}.

Для каждого объекта в перечисляемом блок вызывается с переменными memo и obj:

  • obj - текущий объект в массиве

  • memo - это значение, которое было возвращено последней итерацией вашего блока (для первой итерации это то, что вы вводите)

1 голос
/ 25 сентября 2018

Это можно сделать довольно кратко, используя each_with_object.

array.each_with_object({}) { |(k, v), h| h[k] = (h[k] || []) + [v] }

Демонстрация в irb:

irb(main):002:0> array = [[:a,:b],[:a,:c],[:c,:b]]
=> [[:a, :b], [:a, :c], [:c, :b]]
irb(main):003:0> array.each_with_object({}) { |(k, v), h| h[k] = (h[k] || []) + [v] }
=> {:a=>[:b, :c], :c=>[:b]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...