Tengo una matriz simple:
arr = ["apples", "bananas", "coconuts", "watermelons"]
También tengo una función f
que 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).*
yflatten
para 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).
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.
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_h
mé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
zip
tanto 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
inject
método: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-injectfuente
merge
para 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.7
hasta1.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