Преобразование массива хешей в хэш-хеши, индексированные атрибутом хэшей

У меня есть массив хешей, представляющих объекты в качестве ответа на вызов API. Мне нужно извлечь данные из некоторых хешей, а один конкретный ключ служит идентификатором хэш-объекта. Я хотел бы преобразовать массив в хэш с ключами как идентификаторы, а значения - как исходный хэш с этим идентификатором.

Вот что я говорю:

api_response = [
  { :id => 1, :foo => 'bar' },
  { :id => 2, :foo => 'another bar' },
  # ..
]

ideal_response = {
  1 => { :id => 1, :foo => 'bar' },
  2 => { :id => 2, :foo => 'another bar' },
  # ..
}

Есть два способа, которыми я мог бы это сделать.

  • Сопоставьте данные с ideal_response (ниже)
  • Используйте api_response.find { |x| x[:id] == i } для каждой записи, к которой мне нужно обратиться.
  • Метод, о котором я не знаю, возможно, используя способ использования map для создания хэша, изначально.

Мой метод отображения:

keys = data.map { |x| x[:id] }
mapped = Hash[*keys.zip(data).flatten]

Я не могу не чувствовать, что есть более совершенный, более аккуратный способ сделать это. Вариант 2 очень эффективен, когда существует очень минимальное количество записей, к которым необходимо получить доступ. Отображение здесь отличается, но оно начинает разрушаться, когда в ответе много записей. К счастью, я не ожидаю, что там будет более 50-100 записей, поэтому сопоставления достаточно.

Есть ли более разумный, более аккуратный или более эффективный способ сделать это в Ruby?

Ответы

Ответ 1

Ruby & lt; = 2.0

> Hash[api_response.map { |r| [r[:id], r] }]
#=> {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}} 

Однако Hash :: [] довольно уродлив и нарушает обычный поток ООП слева направо. Вот почему Facets предложил Enumerable # mash:

> require 'facets'
> api_response.mash { |r| [r[:id], r] }
#=> {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}} 

Эту базовую абстракцию (преобразование перечислимых в хеш-коды) давно просили включить в Ruby, увы, без везения.

Ruby> = 2.1

[ОБНОВЛЕНИЕ] Все еще не люблю Enumerable#mash, но теперь у нас есть Массив # to_h. Не идеально -because, нам нужен промежуточный array-, но лучше, чем ничего:

> object = api_response.map { |r| [r[:id], r] }.to_h

Ответ 2

Что-то вроде:

ideal_response = api_response.group_by{|i| i[:id]} 
#=> {1=>[{:id=>1, :foo=>"bar"}], 2=>[{:id=>2, :foo=>"another bar"}]}

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

Доступ к определенному элементу легко:

ideal_response[1][0]       #=> {:id=>1, :foo=>"bar"}
ideal_response[1][0][:foo] #=> "bar"

То, как вы показываете в конце вопроса, - это еще один действительный способ сделать это. Оба они достаточно быстры и элегантны.

Ответ 3

Для этого я бы просто пошел:

ideal_response = api_response.each_with_object(Hash.new) { |o, h| h[o[:id]] = o }

Не супер красиво с несколькими скобками в блоке, но он делает трюк только с одной итерацией api_response.