Преобразование массива хешей в хэш-хеши, индексированные атрибутом хэшей
У меня есть массив хешей, представляющих объекты в качестве ответа на вызов 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.