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?

... { |v| [v, f(v)] }, ¡pero esto funcionó!*lado*arr.collect?*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).*yflattenpara 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.Hash[*key_pairs.flatten]es simplementeHash[key_pairs]. Mucho mejor, yrequire 'backports'si aún no ha actualizado desde 1.8.6.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).
arres 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.
fuente
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }fuente
Ruby 2.6.0 habilita una sintaxis más corta pasando un bloque al
to_hmétodo :arr.to_h { |a| [a, f(a)] }fuente
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?
fuente
ziptanto como el próximo chico, pero como ya estamos llamandomap, ¿por qué no dejarlo así?h = Hash[ arr.map { |v| [ v, f(v) ] } ]¿Hay alguna ventaja en tu versión que no veo?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-injectfuente
mergepara cada paso de la iteración. Fusionar es O (n), al igual que la iteración. Por lo tanto, esto esO(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 desde0.7hasta1.1 seconds. ¡Sí, eso es una desaceleración por Factor 700 !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 >fuente
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 }fuente