Pregunta

En Ruby, dada una matriz 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}
¿Fue útil?

Solución

NOTA:Para obtener una solución concisa y eficiente, consulte La respuesta de Marc-André Lafortune abajo.

Esta respuesta se ofreció originalmente como una alternativa a los enfoques que utilizan aplanar, que fueron los más votados al 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! Soluciones usando aplanar ¡No conservará las claves o valores de la matriz!

Basándonos en la popular respuesta 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 (p. ej.['k1','v1,'k2','v2']).Lo que es peor es que una matriz diferente que se aplanara a una longitud uniforme simplemente nos daría silenciosamente un Hash con valores incorrectos.

Si desea utilizar claves o valores de matriz, puede utilizar mapa:

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

Esto conserva la clave Array:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

Otros consejos

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 recursividad así Array Las claves y los valores funcionan como se esperaba.

La mejor manera es usar Array#to_h:

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

Tenga en cuenta que to_h también acepta un bloque:

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

Nota: to_h acepta un bloque en Ruby 2.6.0+;para rubíes tempranos puedes usar mi backports gema y require 'backports/2.6.0/enumerable/to_h'

to_h sin bloque se introdujo en Ruby 2.1.0.

Antes de Ruby 2.1, se podía utilizar el formato menos legible. Hash[]:

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

Finalmente, tenga cuidado con cualquier solución que utilice flatten, esto podría crear problemas con valores que son matrices en sí mismas.

Actualizar

Ruby 2.1.0 se lanza hoy.y yo vengo con Array#to_h (Notas de lanzamiento y ruby-doc), que resuelve el problema de convertir un Array a un Hash.

Ejemplo de documentos Ruby:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}

Editar:Vi las respuestas publicadas mientras escribía, Hash[a.flatten] parece el camino a seguir.Debo haber perdido esa parte de 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 sencilla:

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

a = matriz, h = hash, r = hash del valor de retorno (en el que acumulamos), i = elemento de la matriz

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

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

También puedes 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} 

Agregando a la respuesta pero usando matrices anónimas y anotando:

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

Desmontando esa respuesta, comenzando desde adentro:

  • "a,b,c,d" es en realidad una cadena.
  • split sobre comas en una matriz.
  • zip eso 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 lo transforma a:

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

y luego:

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

que podemos utilizar como argumentos para la Hash[] método:

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

cuyos rendimientos:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}

Resumen y 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:

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

Siguen la discusión y los detalles.


Configuración:variables

Para mostrar los datos que usaremos desde el principio, crearé algunas variables para representar varias posibilidades para los datos.Encajan en las siguientes 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 lo han hecho otros, usaré cadenas de aquí en adelante para que la entrada y los resultados coincidan).

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

Claves y/o valores multivalor, como a3:

En algunas otras respuestas, se presentó otra posibilidad (que amplío aquí): las claves y/o los valores pueden ser matrices por sí solos:

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

Matriz desequilibrada, como a4:

Por si acaso, pensé en agregar uno para el caso en el que podríamos tener una entrada incompleta:

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 respaldado a versiones anteriores).Para una matriz inicialmente plana, esto no funciona:

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

Usando Hash::[] combinado con el operador de salpicaduras hace:

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

Entonces esa es la solución para el caso simple representado por a1.

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

Con una variedad de [key,value] escriba matrices, hay dos maneras de hacerlo.

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 el caso de una matriz anidada simple.

Esto sigue siendo cierto incluso con submatrices 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 púas (las estructuras anómalas dan problemas):

Si recibimos datos de entrada que no están balanceados, tendremos problemas con #to_h:

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

Pero Hash::[] todavía funciona, solo configurando nil como el valor de 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: uso de nuevas variables a5 y a6

Algunas otras respuestas mencionadas flatten, con o sin 1 argumento, así que creemos 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 datos base debido al problema de equilibrio que tuvimos, que apareció con a4.to_h.me imagino llamando flatten podría ser un enfoque que alguien podría usar para intentar 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 primera vista, esto parece funcionar, pero nos hizo empezar con el pie izquierdo con las naranjas sin semillas, lo que también nos hizo 3 a llave y durian a valor.

Y esto, como ocurre con a1, simplemente no funciona:

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

Entonces a4.flatten no es útil para nosotros, solo queremos usar Hash[a4]

El flatten(1) caso (a6):

Pero ¿qué pasa con el aplanamiento sólo parcial?Vale la pena señalar que llamar 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 preaplanada, todavía anidada (forma alternativa de obtener a6):

Pero, ¿y si así fuera como obtuvimos la matriz en primer lugar?(Es decir, en comparación con 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é pasaría si todavía quisiéramos obtener el comportamiento donde último elemento (¡importante!ver más abajo) 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 la clave/valor pares como elementos en la matriz exterior:

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

Tenga en cuenta que esto termina proporcionándonos una nueva matriz que no es "idéntico" a a4, pero tiene la mismos valores:

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

Y así podemos volver a utilizar 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 señalar que el each_slice(2) La solución sólo hace que las cosas vuelvan a la normalidad si último La clave era la que faltaba un valor.Si luego agregamos un par clave/valor adicional:

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 aspectos importantes:

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 sólo para que sea más fácil mostrar la estructura aquí;no hay ningún requisito conceptual para esto.)

Entonces el each_slice La solución para una entrada plana desequilibrada solo funciona si el bit desequilibrado está al final.


Comidas para llevar:

  1. Siempre que sea posible, establezca entradas para estas cosas como [key, value] pares (una submatriz para cada elemento de la matriz exterior).
  2. Cuando realmente puedas hacer eso, tampoco #to_h o Hash::[] Ambos funcionarán.
  3. Si no puedes, Hash::[] combinado con el splat (*) trabajará, siempre que las entradas estén equilibradas.
  4. Con un desequilibrado y departamento matriz como entrada, la única forma en que esto funcionará razonablemente es si el último value El artículo 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 leí) dio una respuesta tan completa como me esfuerzo por dar aquí.Espero que sea de ayuda.Sin embargo, doy gracias a quienes me precedieron, varios de los cuales me inspiraron para partes de esta respuesta.

si tiene una matriz que se parece a esta:

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]}

No estoy seguro 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

Si los valores numéricos son índices secuenciales, entonces podríamos tener formas más simples...Aquí está el envío de mi código. Mi Ruby está 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}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top