Cuál es la mejor manera de convertir un array a un hash en Ruby


En Ruby, dado un array en una de las siguientes formas...

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

...¿cuál es la mejor manera de convertir esto en un hash en forma de...

{apple => 1, banana => 2}
Author: Max, 2008-09-02

11 answers

NOTA : Para una solución concisa y eficiente, consulte la respuesta de Marc-André Lafortune a continuación.

Esta respuesta se ofreció originalmente como una alternativa a los enfoques que utilizan flatten, que fueron los más votados en el momento de escribir este artículo. Debería haber aclarado que no tenía la intención de presentar este ejemplo como una mejor práctica o un enfoque eficiente. La respuesta original sigue.


Advertencia! Las soluciones que utilizan aplanar no preservarán Llaves o valores de array!

Basándonos en la respuesta popular de @John Topley, intentemos:

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

Esto arroja un error:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

El constructor esperaba una matriz de longitud uniforme (por ejemplo, ['k1','v1,'k2','v2']). Lo que es peor es que una matriz diferente que se aplanó a una longitud uniforme simplemente nos daría silenciosamente un Hash con valores incorrectos.

Si desea usar claves o valores de matriz, puede usar map :

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

Esto preserva el Array clave:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
 83
Author: Stew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:01

Simplemente use Hash[*array_variable.flatten]

Por ejemplo:

a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"

a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"

Usando Array#flatten(1) limita la recursión para que Array las claves y los valores funcionen como se espera.

 142
Author: John Topley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-09 04:34:16

La mejor manera es utilizar Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {:apple => 1, :banana => 2}

Nota: Esto se introdujo en Ruby 2.1.0. Para Rubíes más antiguos, puedes usar mi gema backports y require 'backports/2.1.0/array/to_h', o bien utilizar Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

Esto está disponible en Ruby 1.8.7 y versiones posteriores. Si todavía estás usando Ruby 1.8.6 podrías require "backports/1.8.7/hash/constructor", pero entonces también podría usar el backport to_h.

Finalmente, mientras que muchas soluciones usan flatten, esto podría crear problemas con los valores que son arrays ellos mismos.

 66
Author: Marc-André Lafortune,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-29 23:58:09

Actualización

Ruby 2.1.0 se lanza hoy . Y yo vengo con Array#to_h (notas de la versión y ruby-doc), que resuelve el problema de convertir un Array a un Hash.

Ruby docs ejemplo:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}
 19
Author: JanDintel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-25 22:22:36

Editar: Al ver las respuestas publicadas mientras escribía, Hash[a.flatten] parece el camino a seguir. Debe haber perdido esa parte en la documentación cuando estaba pensando en la respuesta. Pensé que las soluciones que he escrito se pueden utilizar como alternativas si es necesario.

La segunda forma es más simple:

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

A = array, h = hash, r = return-value hash (el que acumulamos en), i = item en el array

La forma más ordenada que se me ocurre de hacer la primera la forma es algo como esto:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
 9
Author: Daemin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-02 14:23:15

También puede simplemente convertir una matriz 2D en hash usando:

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

 => {1=>2, 3=>4} 
 5
Author: Priyanka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-11 14:12:16

Añadiendo a la respuesta pero usando arrays anónimos y anotando:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

Tomando esa respuesta aparte, comenzando desde el interior: {[13]]}

  • "a,b,c,d" es en realidad una cadena.
  • split en comas en una matriz.
  • zip que, junto con la siguiente matriz.
  • [1,2,3,4] es una matriz real.

El resultado intermedio es:

[[a,1],[b,2],[c,3],[d,4]]

Aplanar luego transforma eso a:

["a",1,"b",2,"c",3,"d",4]

Y luego:

*["a",1,"b",2,"c",3,"d",4] desenrolla que en "a",1,"b",2,"c",3,"d",4

Que podemos usar como argumentos para el método Hash[]:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

Que produce:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
 2
Author: StevenJenkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-02 19:34:43

Resumen & TL; DR:

Esta respuesta espera ser un resumen completo de la información de otras respuestas.

La versión muy corta, dados los datos de la pregunta más un par de extras: {[80]]}

flat_array   = [  apple, 1,   banana, 2  ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [  apple, 1,   banana     ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana   ] ] # count=2 of either k or k,v arrays


# there's one option for flat_array:
h1  = Hash[*flat_array]                     # => {apple=>1, banana=>2}

# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0    => {apple=>1, banana=>2}
h2b = Hash[nested_array]                    # => {apple=>1, banana=>2}

# ok if *only* the last value is missing:
h3  = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4  = Hash[incomplete_n] # or .to_h           => {apple=>1, banana=>nil}

# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3  # => false
h3 == h4  # => true

La discusión y los detalles siguen.


Configuración: variables

Para mostrar los datos que usaremos por adelantado, crearé algunas variables para representar varias posibilidades para los datos. Encajan en lo siguiente categorías:

Basado en lo que estaba directamente en la pregunta, como a1 y a2:

(Nota: Supongo que apple y banana estaban destinados a representar variables. Como otros han hecho, usaré cadenas de aquí en adelante para que la entrada y los resultados puedan coincidir.)

a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input

Claves y/o valores de múltiples valores, como a3:

En algunas otras respuestas, se presentó otra posibilidad – que explico aquí): las claves y / o los valores pueden ser matrices en su propio:

a3 = [ [ 'apple',                   1   ],
       [ 'banana',                  2   ],
       [ ['orange','seedless'],     3   ],
       [ 'pear',                 [4, 5] ],
     ]

Matriz no balanceada, como a4:

Para una buena medida, pensé en agregar uno para un caso en el que podríamos tener una entrada incompleta: {[80]]}

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

Ahora, a trabajar:

Comenzando con una matriz inicialmente plana, a1:

Algunos han sugerido usar #to_h (que apareció en Ruby 2.1.0, y puede ser backported a versiones anteriores). Para una matriz inicialmente plana, esto no funciona:

a1.to_h   # => TypeError: wrong element type String at 0 (expected array)

Utilizando Hash::[] combinado con el operador splat hace:

Hash[*a1] # => {"apple"=>1, "banana"=>2}

Así que esa es la solución para el caso simple representado por a1.

Con una matriz de matrices de pares clave/valor, a2:

Con una matriz de matrices de tipo [key,value], hay dos maneras de ir.

Primero, Hash::[] todavía funciona (como lo hizo con *a1):

Hash[a2] # => {"apple"=>1, "banana"=>2}

Y luego también #to_h funciona ahora:

a2.to_h  # => {"apple"=>1, "banana"=>2}

Entonces, dos respuestas fáciles para la matriz anidada simple caso.

Esto sigue siendo cierto incluso con sub-arrays como claves o valores, como con a3:

Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]} 
a3.to_h  # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}

Pero los durianos tienen picos (las estructuras anómalas dan problemas):{[108]]}

Si hemos obtenido datos de entrada que no están equilibrados, nos encontraremos con problemas con #to_h:

a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)

Pero Hash::[] todavía funciona, simplemente estableciendo nil como el valor para durian (y cualquier otro elemento de matriz en a4 que sea solo una matriz de 1 valor):

Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}

Aplanamiento-usando nuevas variables a5 y a6

Algunas otras respuestas mencionadas flatten, con o sin un argumento 1, así que vamos a crear algunas variables nuevas:

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

Elegí usar a4 como la base de datos debido al problema de equilibrio que teníamos, que apareció con a4.to_h. Me imagino que llamar flatten podría ser un enfoque que alguien podría usar para tratar de resolver eso, que podría verse como el siguiente.

flatten sin argumentos (a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

A una mirada ingenua, esto aparece para trabajar-pero nos puso con el pie izquierdo con las naranjas sin semillas, así también haciendo 3 a clave y durian a valor.

Y esto, como con a1, simplemente no funciona:

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

Así que a4.flatten no nos es útil, solo queremos usar Hash[a4]

El caso flatten(1)(a6):

Pero ¿qué pasa con el aplanamiento solo parcial? Vale la pena señalar que llamar a Hash::[] usando splat en la matriz parcialmente aplanada (a6) es no lo mismo que llamar Hash[a4]:

Hash[*a6] # => ArgumentError: odd number of arguments for Hash

Matriz pre-aplanada, aún anidada (forma alternativa de obtener a6):

Pero, ¿y si así fue como obtuvimos la matriz en primer lugar? (Es decir, comparativamente a a1, fueron nuestros datos de entrada, solo que esta vez algunos de los datos pueden ser matrices u otros objetos. Hemos visto que Hash[*a6] no funciona, pero ¿qué pasa si todavía queremos obtener el comportamiento donde el último elemento (importante! véase más adelante) actuó como clave para un nil ¿valor?

En tal situación, todavía hay una manera de hacer esto, usando Enumerable#each_slice para volver a key / value pares como elementos en el array exterior:

a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 

Tenga en cuenta que esto termina obteniendo un nuevo array que no es "idéntico " a a4, pero tiene los mismos valores :

a4.equal?(a7) # => false
a4 == a7      # => true

Y así podemos usar de nuevo Hash::[]:

Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]

Pero hay un problema!

Es importante tener en cuenta que el each_slice(2) la solución solo devuelve las cosas a la cordura si el último la clave era la que faltaba un valor. Si más tarde añadimos un par clave/valor extra:

a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # multi-value key
#     ["durian"],              # missing value
#     ["lychee", 4]]           # new well-formed item

a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]

a7_plus = a6_plus.each_slice(2).to_a
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # so far so good
#     ["durian",               "lychee"], # oops! key became value!
#     [4]]                     # and we still have a key without a value

a4_plus == a7_plus # => false, unlike a4 == a7

Y los dos hashes que obtendríamos de esto son diferentes en formas importantes: {[80]]}

ap Hash[a4_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => nil, # correct
                    "lychee" => 4    # correct
}

ap Hash[a7_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => "lychee", # incorrect
                           4 => nil       # incorrect
}

(Nota: Estoy usando awesome_print's ap solo para que sea más fácil mostrar la estructura aquí; no hay ningún requisito conceptual para esto.)

Entonces la solución each_slice para una entrada plana no balanceada solamente funciona si el bit desequilibrado está al final.


Para llevar:

  1. Siempre que sea posible, configure la entrada de estas cosas como pares [key, value] (un sub-array para cada elemento en el array exterior).
  2. Cuando realmente puedes hacer eso, #to_h o Hash::[] ambos funcionarán.
  3. Si no puedes, Hash::[] combinado con el splat (*) funcionará, siempre y cuando las entradas estén balanceadas.
  4. Con un desequilibrado y plano array como entrada, la única forma en que esto funcionará razonablemente es si el último value item es el único que falta.

Nota al margen: Estoy publicando esta respuesta porque siento que hay valor que agregar-algunas de las respuestas existentes tienen información incorrecta, y ninguna (que he leído) dio una respuesta tan completa como estoy tratando de hacer aquí. Espero que sea útil. Sin embargo, doy gracias a los que me precedieron, varios de los cuales proporcionó inspiración para partes de esta respuesta.

 1
Author: lindes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-03 16:28:31

Si usted tiene matriz que se parece a esto -

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

Y desea que los primeros elementos de cada matriz se conviertan en las claves para el hash y que el resto de los elementos se conviertan en matrices de valores, entonces puede hacer algo como esto:

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
 0
Author: user3588841,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-12 11:27:22

No estoy seguro de si es la mejor manera, pero esto funciona:

a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
  m1[a[x*2]] = a[x*2 + 1]
end

b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
  m2[x] = y
end
 0
Author: Anders Sandvig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-03 16:33:37

Si los valores numéricos son índices seq, entonces podríamos tener formas más simples... Aquí está mi envío de código, mi Ruby es un poco oxidado

   input = ["cat", 1, "dog", 2, "wombat", 3]
   hash = Hash.new
   input.each_with_index {|item, index|
     if (index%2 == 0) hash[item] = input[index+1]
   }
   hash   #=> {"cat"=>1, "wombat"=>3, "dog"=>2}
 -1
Author: Gishu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-02 14:20:27