2011-01-20 28 views
15

Tengo una matriz de hashes que representan objetos como respuesta a una llamada API. Necesito extraer datos de algunos de los hash, y una clave particular sirve como id para el objeto hash. Me gustaría convertir la matriz en un hash con las claves como identificadores, y los valores como el hash original con esa identificación.Convertir matriz de hashes en hash-of-hashes, indexado por un atributo de los hash

Aquí es lo que estoy hablando:

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

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

Hay dos maneras que podría pensar en hacer esto.

  1. en el mapa los datos a la ideal_response (continuación)
  2. Uso api_response.find { |x| x[:id] == i } para cada registro que necesito para acceder.
  3. Un método que desconozco, posiblemente involucrando una forma de usar map para construir un hash, de forma nativa.

Mi método de mapeo:

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

no puedo evitar sentir que hay un camino con más prestaciones, más ordenado de hacer esto. La opción 2 es muy efectiva cuando hay una cantidad mínima de registros a los que se debe acceder. El mapeo se destaca aquí, pero comienza a descomponerse cuando hay muchos registros en la respuesta. Afortunadamente, no espero que haya más de 50-100 registros, por lo que el mapeo es suficiente.

¿Hay alguna manera más inteligente, ordenada o más eficiente de hacer esto en Ruby?

Respuesta

18

Rubí < = 2,0

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

Sin embargo, Hash::[] es bastante feo y se rompe el flujo de programación orientada a objetos de izquierda a derecha de costumbre. Es por eso que Facets propuso Enumerable#mash: se le pidió

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

Esta abstracción básica (enumerables convertir a los hashes) para ser incluido en Ruby hace mucho tiempo, por desgracia, without luck.

Ruby> = 2.1

[ACTUALIZACIÓN] Todavía no hay amor por Enumerable#mash, pero ahora tenemos Array#to_h. No -porque ideales necesitamos una Expandido intermedia, pero mejor que nada:

# ruby 2.1 
api_response.map { |r| [r[:id], r] }.to_h 
0

Algo así como:

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

Utiliza Enumerable de group_by, que trabaja en colecciones, volviendo coincidencias para cualquier valor de la clave que desea. Debido a que espera encontrar múltiples ocurrencias de coincidencia de valores-clave, los agrega a las matrices, por lo que termina con un hash de matrices de hashes. Podría pelar las matrices internas si quisiera, pero podría correr el riesgo de sobrescribir el contenido si dos de sus ID de hash colisionan. group_by lo evita con la matriz interna.

Acceso a un elemento en particular es fácil:

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

La forma en que muestras al final de la cuestión es otra forma válida de hacerlo. Ambos son razonablemente rápidos y elegantes.

+0

Esto es mejor que el mapeo que tuve en términos de rendimiento y orden, pero acceder a los registros a través de 'data [id] [0]' o 'data [id] .first' es un poco engorroso. Sin embargo, me estoy poniendo nervioso en este punto. Veamos si a alguien más le importa arrojar su sombrero al ring. – coreyward

+0

Eso es el resultado de la posibilidad de encontrar una clave duplicada. 'group_by' es una rutina genérica diseñada para seguridad con datos desconocidos. Tener conocimientos específicos de las aplicaciones, como saber que nunca, nunca, encontrará un duplicado y la consiguiente colisión de claves y pérdida de datos, permitiría simplificar la estructura de datos en un nivel. –

0

Por esta probablemente me acaba de ir:

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

No es super bonita con los múltiples soportes en el bloque pero hace el truco con solo una iteración de api_response.

Cuestiones relacionadas