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}
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}
Respuestas:
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 usan aplanar, que fueron los más votados al momento de escribir. 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 usan flatten no conservarán las claves o valores de la matriz!
Sobre la base de 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 par (por ejemplo, ['k1', 'v1,' k2 ',' v2 ']). Lo peor es que una matriz diferente que se aplanó a una longitud uniforme simplemente nos daría un Hash con valores incorrectos en silencio.
Si desea usar claves o valores de matriz, puede usar map :
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
Esto conserva la clave de matriz:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
h3 = Hash[*a3.flatten(1)]
lugar de lo h3 = Hash[*a3.flatten]
cual arrojaría un error.
to_h
es mejor.
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}"
El uso Array#flatten(1)
limita la recursividad para que las Array
claves y los valores funcionen como se esperaba.
Hash[*ary.flatten(1)]
, que conservará las claves y los valores de la matriz. Es lo recursivo lo flatten
que los está destruyendo, lo cual es bastante fácil de evitar.
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 bloqueo:
[: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 los primeros rubíes puedes usar mi backports
gema yrequire 'backports/2.6.0/enumerable/to_h'
to_h
sin bloque se introdujo en Ruby 2.1.0.
Antes de Ruby 2.1, uno podría usar lo menos legible Hash[]
:
array = [ [:apple,1],[:banana,2] ]
Hash[ array ] #= > {:apple => 1, :banana => 2}
Finalmente, tenga cuidado con cualquier solución que use flatten
, esto podría crear problemas con los valores que son matrices en sí.
to_h
método mejor que las respuestas anteriores porque expresa la intención de convertir después de operar en la matriz.
Array#to_h
tampoco Enumerable#to_h
está en core ruby 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
y quiero la salida como {"apple" =>[1,3], "banana"=>[2,4]}
?
Actualizar
Ruby 2.1.0 se lanza hoy . Y vengo con Array#to_h
( notas de la versión y ruby-doc ), que resuelve el problema de convertir unArray
a una Hash
.
Ruby docs ejemplo:
[[: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. Debí haber perdido ese bit en la documentación cuando estaba pensando en la respuesta. Pensé que las soluciones que he escrito pueden usarse 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 = matriz, h = hash, r = hash de valor de retorno (en el que acumulamos), i = elemento en la matriz
La mejor manera en que puedo pensar en hacer la primera forma es algo como esto:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
a.inject({})
one-liner que permite asignaciones de valor más flexibles.
h = {}
del segundo ejemplo mediante el uso de inyectar, terminando cona.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
Esta respuesta espera ser un resumen completo de la información de otras respuestas.
La versión muy corta, dada la información 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
Discusión y detalles siguen.
Para mostrar los datos que usaremos por adelantado, crearé algunas variables para representar varias posibilidades para los datos. Encajan en las siguientes categorías:
a1
y a2
:(Nota: supongo que apple
y banana
estaban destinados a representar variables Como otros han hecho, voy a utilizar cuerdas de aquí en adelante por lo que la entrada y los resultados pueden igualar.).
a1 = [ 'apple', 1 , 'banana', 2 ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
a3
:En algunas otras respuestas, se presentó otra posibilidad (que amplío aquí): las claves y / o valores pueden ser matrices por sí mismos:
a3 = [ [ 'apple', 1 ],
[ 'banana', 2 ],
[ ['orange','seedless'], 3 ],
[ 'pear', [4, 5] ],
]
a4
:En buena medida, pensé que agregaría uno para un 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!
]
a1
:Algunos han sugerido usar #to_h
(que apareció en Ruby 2.1.0, y puede ser compatible con versiones anteriores). Para una matriz inicialmente plana, esto no funciona:
a1.to_h # => TypeError: wrong element type String at 0 (expected array)
El uso Hash::[]
combinado con el operador splat hace:
Hash[*a1] # => {"apple"=>1, "banana"=>2}
Entonces esa es la solución para el caso simple representado por a1
.
a2
:Con una matriz de matrices de [key,value]
tipos, hay dos formas 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 matriz anidada simple.
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]}
Si hemos recibido datos de entrada que no están equilibrados, tendremos problemas con #to_h
:
a4.to_h # => ArgumentError: wrong array length at 3 (expected 2, was 1)
Pero Hash::[]
aún funciona, solo estableciendo nil
como el valor para durian
(y cualquier otro elemento de matriz en a4 que sea solo una matriz de valor 1):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
a5
ya6
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ó a4.to_h
. Me imagino llamandoflatten
podría ser un enfoque que alguien podría usar para tratar de resolver eso, que podría ser 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 simple vista, esto parece funcionar, pero nos puso en el camino equivocado con las naranjas sin semillas, por lo que también es 3
una clave y durian
un valor .
Y esto, como con a1
, simplemente no funciona:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Así a4.flatten
que no es útil para nosotros, solo queremos usarHash[a4]
flatten(1)
caso ( a6
):Pero, ¿qué pasa con el aplanamiento parcial? Vale la pena señalar que llamar Hash::[]
usando splat
en la matriz parcialmente aplanada ( a6
) no es lo mismo que llamar Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Pero, ¿qué pasaría si así fue como obtuvimos la matriz en primer lugar? (Es decir, en comparación con a1
nuestros datos de entrada, solo que esta vez algunos de los datos pueden ser matrices u otros objetos). Hemos visto que eso Hash[*a6]
no funciona, pero ¿y si todavía quisiéramos obtener el comportamiento donde ¿El ú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, utilizando Enumerable#each_slice
para volver a los pares clave / valor como elementos en la matriz externa:
a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]]
Tenga en cuenta que esto termina obteniendo una nueva matriz que no es " idéntica " a4
, pero tiene los mismos valores :
a4.equal?(a7) # => false
a4 == a7 # => true
Y así podemos usar nuevamente Hash::[]
:
Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]
Es importante tener en cuenta que la each_slice(2)
solución solo devuelve las cosas a la cordura si la última clave fue la que falta 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 formas 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 para mostrar la estructura aquí, no hay requisito conceptual para esto)
Entonces, la each_slice
solución para una entrada plana no balanceada solo funciona si el bit no balanceado está al final.
[key, value]
pares (una submatriz para cada elemento en la matriz externa).#to_h
o Hash::[]
ambos funcionarán.Hash::[]
combinado con el splat ( *
) funcionará, siempre que las entradas estén equilibradas .value
elemento es el único que falta.Nota al margen: estoy publicando esta respuesta porque siento que hay valor para agregar: algunas de las respuestas existentes tienen información incorrecta, y ninguna (que leí) dio una respuesta tan completa como me estoy esforzando por hacer aquí. Espero que sea útil. Sin embargo, doy gracias a los que vinieron antes que yo, varios de los cuales inspiraron algunas partes de esta respuesta.
Anexando la respuesta pero usando matrices anónimas y anotaciones:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
Tomando esa respuesta aparte, comenzando desde adentro:
"a,b,c,d"
es en realidad una cadenasplit
en comas en una matriz.zip
eso junto con la siguiente matriz.[1,2,3,4]
es una matriz realEl 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 entonces:
*["a",1,"b",2,"c",3,"d",4]
desenrolla eso en
"a",1,"b",2,"c",3,"d",4
que podemos usar como argumentos del 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}
*
) y flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
. Más detalles en una respuesta que agregué.
si tiene una matriz que se ve así:
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 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 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
Si los valores numéricos son índices seq, entonces podríamos tener formas más simples ... Aquí está mi envío de código, My 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}