En Ruby, ¿cómo hago un hash a partir de una matriz?


76

Tengo una matriz simple:

arr = ["apples", "bananas", "coconuts", "watermelons"]

También tengo una función fque realizará una operación en una sola entrada de cadena y devolverá un valor. Esta operación es muy cara, por lo que me gustaría memorizar los resultados en el hash.

Sé que puedo hacer el hash deseado con algo como esto:

h = {}
arr.each { |a| h[a] = f(a) }

Lo que me gustaría hacer es no tener que inicializar h, para poder escribir algo como esto:

h = arr.(???) { |a| a => f(a) }

¿Se puede hacer eso?

Respuestas:


128

Supongamos que tiene una función con un nombre funcional: "f"

def f(fruit)
   fruit + "!"
end

arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]

Te regalaré:

{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}

Actualizado:

Como se menciona en los comentarios, Ruby 1.8.7 introduce una sintaxis más agradable para esto:

h = Hash[arr.collect { |v| [v, f(v)] }]

Creo que quisiste decir ... { |v| [v, f(v)] }, ¡pero esto funcionó!
Wizzlewott

3
Solo una cosa: ¿por qué hay una al *lado *arr.collect?
Jeriko

3
@Jeriko: el operador splat *recopila una lista en una matriz o desenrolla una matriz en una lista, según el contexto. Aquí desenrolla la matriz en una lista (que se utilizará como elementos para el nuevo hash).
Telemachus

2
Después de mirar la respuesta de Jörg y pensando en esto durante un poco más, nota que se puede eliminar tanto *y flattenpara una versión más simple: h = Hash[ arr.collect { |v| [ v, f(v) ] } ]. Sin embargo, no estoy seguro de si hay un problema que no veo.
Telemachus

3
En Ruby 1.8.7, lo feo Hash[*key_pairs.flatten]es simplemente Hash[key_pairs]. Mucho mejor, y require 'backports'si aún no ha actualizado desde 1.8.6.
Marc-André Lafortune

56

Hice algunos puntos de referencia rápidos y sucios en algunas de las respuestas dadas. (Es posible que estos hallazgos no sean exactamente idénticos a los suyos según la versión de Ruby, almacenamiento en caché extraño, etc., pero los resultados generales serán similares).

arr es una colección de objetos ActiveRecord.

Benchmark.measure {
    100000.times {
        Hash[arr.map{ |a| [a.id, a] }]
    }
}

Benchmark @ real = 0.860651, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.0, @ utime = 0.8500000000000005, @ total = 0.8500000000000005

Benchmark.measure { 
    100000.times {
        h = Hash[arr.collect { |v| [v.id, v] }]
    }
}

Benchmark @ real = 0.74612, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.010000000000000009, @ utime = 0.740000000000002, @ total = 0.750000000000002

Benchmark.measure {
    100000.times {
        hash = {}
        arr.each { |a| hash[a.id] = a }
    }
}

Benchmark @ real = 0.627355, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.010000000000000009, @ utime = 0.6199999999999974, @ total = 0.6299999999999975

Benchmark.measure {
    100000.times {
        arr.each_with_object({}) { |v, h| h[v.id] = v }
    }
}

Benchmark @ real = 1.650568, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.12999999999999998, @ utime = 1.51, @ total = 1.64

En conclusión

El hecho de que Ruby sea expresiva y dinámica no significa que siempre debas buscar la solución más bonita. El básico de cada ciclo fue el más rápido en la creación de un hash.


7
Tú, amigo mío, eres increíble por hacer tu tarea y publicarla :)
Alexander Bird

Ligeramente más rápido de usar una variable de bucle incrementada manualmente: no tengo su conjunto de datos, acabo de cocinar un objeto trivial con un acceso @id y más o menos coincidí con sus números, pero la iteración directa redujo un par de%. Estilísticamente, prefiero {} .tap {| h | ....} para asignar un hash, porque me gustan los fragmentos encapsulados.
android.weasel

32
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }

2
Esto es mucho más conciso que usar Hash [arr.collect {...}]
kaichanvong

1
Esto es increíblemente lento,
mira


11

Esto es lo que probablemente escribiría:

h = Hash[arr.zip(arr.map(&method(:f)))]

Simple, claro, obvio, declarativo. ¿Qué más podrías querer?


1
Me gusta ziptanto como el próximo chico, pero como ya estamos llamando map, ¿por qué no dejarlo así? h = Hash[ arr.map { |v| [ v, f(v) ] } ]¿Hay alguna ventaja en tu versión que no veo?
Telemachus

@Telemachus: Con todo el código Haskell que he estado leyendo, me acabo de acostumbrar a la programación sin puntos, eso es todo.
Jörg W Mittag

5

Lo estoy haciendo como se describe en este gran artículo http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }

Más información sobre el injectmétodo: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject


Esto hace una mergepara cada paso de la iteración. Fusionar es O (n), al igual que la iteración. Por lo tanto, esto es O(n^2)mientras que el problema en sí es obviamente lineal. En términos absolutos, solo probé esto en una matriz con 100k elementos y tomó 730 seconds, mientras que los otros métodos mencionados en este hilo tomaron desde 0.7hasta 1.1 seconds. ¡Sí, eso es una desaceleración por Factor 700 !
Matthias Winkelmann

1

Otro, un poco más claro en mi humilde opinión:

Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]

Usando longitud como f () -

2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
 => ["apples", "bananas", "coconuts", "watermelons"] 
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
 => {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11} 
2.1.5 :028 >

1

además de la respuesta de Vlado Cingel (todavía no puedo agregar un comentario, así que agregué una respuesta).

Inject también se puede utilizar de esta manera: el bloque debe devolver el acumulador. Solo la asignación en el bloque devuelve el valor de la asignación y se informa de un error.

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }

Evalué las dos versiones: el uso de la combinación duplica el tiempo de ejecución. La versión de inyección anterior es una comparación con la versión de recopilación de microspino
ruud
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.