Array a Hash Ruby

192

De acuerdo, así que aquí está el trato, he estado buscando en Google durante años para encontrar una solución a esto y, aunque hay muchos por ahí, no parecen hacer el trabajo que estoy buscando.

Básicamente tengo una matriz estructurada como esta

["item 1", "item 2", "item 3", "item 4"] 

Quiero convertir esto en un hash para que se vea así

{ "item 1" => "item 2", "item 3" => "item 4" }

es decir, los elementos que están en los índices 'pares' son las claves y los elementos en los índices 'impares' son los valores.

¿Alguna idea de cómo hacer esto limpiamente? Supongo que un método de fuerza bruta sería extraer todos los índices pares en una matriz separada y luego recorrerlos para agregar los valores.

djhworld
fuente

Respuestas:

358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

Eso es. El *se llama el operador splat .

Una advertencia por @Mike Lewis (en los comentarios): "Ten mucho cuidado con esto. Ruby expande los símbolos en la pila. Si haces esto con un gran conjunto de datos, espera volar tu pila".

Por lo tanto, para la mayoría de los casos de uso general, este método es excelente, pero utilice un método diferente si desea realizar la conversión en muchos datos. Por ejemplo, @ Łukasz Niemier (también en los comentarios) ofrece este método para grandes conjuntos de datos:

h = Hash[a.each_slice(2).to_a]
Ben Lee
fuente
10
@tester, *se llama el operador splat . Toma una matriz y la convierte en una lista literal de elementos. Entonces *[1,2,3,4]=> 1, 2, 3, 4. En este ejemplo, lo anterior es equivalente a hacer Hash["item 1", "item 2", "item 3", "item 4"]. Y Hashtiene un []método que acepta una lista de argumentos (haciendo claves de índices pares y valores de índices impares), pero Hash[]no acepta una matriz, por lo que la utilizamos *.
Ben Lee
15
Sé muy cuidadoso con esto. Ruby expande símbolos en la pila. Si hace esto con un gran conjunto de datos, espere volar su pila.
Mike Lewis
9
En las tablas de big data que puede usar Hash[a.each_slice(2).to_a].
Hauleth
44
¿Qué significa "volar tu pila"?
Kevin
66
@Kevin, la pila utiliza un área pequeña de memoria que el programa asigna y reserva para ciertas operaciones específicas. Más comúnmente, se usa para mantener una pila de los métodos que se han llamado hasta ahora. Este es el origen del término seguimiento de pila , y también es la razón por la cual un método infinitamente recursivo puede causar un desbordamiento de pila . El método en esta respuesta también usa la pila, pero dado que la pila es solo un área pequeña de memoria, si prueba este método con una matriz grande, llenará la pila y causará un error (un error en la misma línea que un desbordamiento de pila).
Ben Lee
103

Ruby 2.1.0 introdujo un to_hmétodo en Array que hace lo que usted requiere si su matriz original consiste en matrices de pares clave-valor: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h .

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}
Jochem Schulenklopper
fuente
1
¡Hermoso! Mucho mejor que algunas de las otras soluciones aquí.
Dennis
3
para versiones anteriores a 2.1.0 ruby, puede usar el método Hash :: [] para obtener resultados similares, siempre que tenga pares de una matriz anidada. entonces a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:
@AfDev, de hecho, gracias. Estás en lo correcto (al ignorar los errores tipográficos menores: bardebe ser un símbolo, y el símbolo :2debe ser un número entero. Entonces, tu expresión corregida es a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper
28

Solo use Hash.[]con los valores en la matriz. Por ejemplo:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Arrojar
fuente
1
¿Qué significa [* arr]?
Alan Coromano
1
@Marius: se *arrconvierte arren una lista de argumentos, por lo que se llama al []método de Hash con el contenido de arr como argumentos.
Chuck
26

O si tiene una matriz de [key, value]matrices, puede hacer:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }
Erik Escobedo
fuente
2
Su respuesta no está relacionada con la pregunta y en su caso todavía es mucho más fácil usar la mismaHash[*arr]
Yossi
2
No Volvería { [1, 2] => [3, 4] }. Y dado que el título de la pregunta dice "Array to Hash" y el método incorporado "Hash to Array" lo hace: { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]pensé que más de uno podría terminar aquí tratando de obtener la inversa del método incorporado "Hash to Array". En realidad, así es como terminé aquí de todos modos.
Erik Escobedo
1
Lo siento, agregué un asterisco de repuesto. Hash[arr]hará el trabajo por ti.
Yossi
9
En mi humilde opinión, mejor solución: Hash [* array.flatten (1)]
invitado
2
Yossi: Perdón por resucitar a los muertos, pero hay un problema mayor con su respuesta, y es el uso del #injectmétodo. Con #merge!, #each_with_objectdebería haber sido utilizado. Si #injectse insiste en él, en #mergelugar de #merge!haberlo usado.
Boris Stitnicky
12

Esto es lo que estaba buscando cuando busqué en Google:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}

Karl Glaser
fuente
No desea usar merge, construye y descarta una nueva iteración de hash por ciclo y es muy lenta. Si tiene una variedad de hash, intente en su [{a:1},{b:2}].reduce({}, :merge!)lugar: combina todo en el mismo hash (nuevo).
¡Gracias, esto es lo que yo también quería! :)
Thanasis Petsas
También puedes hacerlo.reduce(&:merge!)
Ben Lee
1
[{a: 1}, {b: 2}].reduce(&:merge!)evalúa a{:a=>1, :b=>2}
Ben Lee
Esto funciona porque inyectar / reducir tiene una característica donde puede omitir el argumento, en cuyo caso utiliza el primer argumento de la matriz para operar como argumento de entrada, y el resto de la matriz como la matriz. Combina eso con symbol-to-proc y obtendrás esta construcción concisa. En otras palabras, [{a: 1}, {b: 2}].reduce(&:merge!)es lo mismo [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }que lo mismo [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee
10

Enumeratorincluye Enumerable. Desde entonces 2.1, Enumerabletambién tiene un método #to_h. Por eso, podemos escribir:

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Porque #each_slicesin bloque nos da Enumerator, y según la explicación anterior, podemos llamar al #to_hmétodo en el Enumeratorobjeto.

Arup Rakshit
fuente
7

Podrías intentarlo así, para una sola matriz

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

para la matriz de la matriz

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}
Jenorish
fuente
6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

o, si odias Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

o, si eres un fanático perezoso de la programación funcional rota:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"
Boris Stitnicky
fuente
Si no odias por completo Hash[ ... ]pero quieres usarlo como un método encadenado (como puedes hacerlo to_h), puedes combinar las sugerencias de Boris y escribir:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios
Para aclarar la semántica del código anterior: Esto creará un "hash perezoso" h , que inicialmente está vacío , y extraerá elementos de la matriz original a cuando sea necesario. ¡Solo entonces se almacenarán en h!
Daniel Werner
1

Todas las respuestas suponen que la matriz inicial es única. OP no especificó cómo manejar matrices con entradas duplicadas, lo que resulta en claves duplicadas.

Miremos a:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Perderá el item 1 => item 2par ya que se anula bij item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Todos los métodos, incluido el reduce(&:merge!) resultado en la misma eliminación.

Sin embargo, podría ser que esto es exactamente lo que esperas. Pero en otros casos, es probable que desee obtener un resultado con un Arrayvalor for:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

La forma ingenua sería crear una variable auxiliar, un hash que tenga un valor predeterminado y luego completarlo en un bucle:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Puede ser posible usar assocy reducehacer lo anterior en una línea, pero eso se vuelve mucho más difícil de razonar y leer.

Berkes
fuente