que - hash.map ruby
Cortar hash de parámetros para valores especĂficos (5)
Aquí están mis implementaciones; Analizaré y aceptaré soluciones más rápidas (o suficientemente más elegantes):
# Implementation 1
class Hash
def slice(*keys)
Hash[keys.zip(values_at *keys)]
end
end
# Implementation 2
class Hash
def slice(*keys)
{}.tap{ |h| keys.each{ |k| h[k]=self[k] } }
end
end
# Implementation 3 - silently ignore keys not in the original
class Hash
def slice(*keys)
{}.tap{ |h| keys.each{ |k| h[k]=self[k] if has_key?(k) } }
end
end
Resumen
Dado un Hash, ¿cuál es la forma más eficiente de crear un subconjunto Hash basado en una lista de claves para usar?
h1 = { a:1, b:2, c:3 } # Given a hash...
p foo( h1, :a, :c, :d ) # ...create a method that...
#=> { :a=>1, :c=>3, :d=>nil } # ...returns specified keys...
#=> { :a=>1, :c=>3 } # ...or perhaps only keys that exist
Detalles
El conjunto de herramientas de la base de datos Sequel le permite a uno crear o actualizar una instancia modelo al pasar un Hash:
foo = Product.create( hash_of_column_values )
foo.update( another_hash )
El marco web de Sinatra pone a disposición un Hash llamado params
que incluye variables de forma, parámetros de cadena de consulta y también enruta coincidencias.
Si creo un formulario que contenga solo campos con el mismo nombre que las columnas de la base de datos y lo publico en esta ruta, todo funciona muy cómodamente:
post "/create_product" do
new_product = Product.create params
redirect "/product/#{new_product.id}"
end
Sin embargo, esto es frágil y peligroso. Es peligroso porque un pirata informático malintencionado podría publicar un formulario con columnas que no están destinadas a ser modificadas y actualizarlas. Es frágil porque usar la misma forma con esta ruta no funcionará:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
product.update(params)
#=> <Sequel::Error: method foo= doesn''t exist or access is restricted to it>
end
end
Entonces, para ser robusto y seguro, quiero poder escribir esto:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
# Only update two specific fields
product.update(params.slice(:name,:description))
# The above assumes a Hash (or Sinatra params) monkeypatch
# I will also accept standalone helper methods that perform the same
end
end
... en lugar de la opción más detallada y no SECA:
post "/update_product/:foo" do |prod_id|
if product = Product[prod_id]
# Only update two specific fields
product.update({
name:params[:name],
description:params[:description]
})
end
end
Actualización: puntos de referencia
Estos son los resultados de la evaluación comparativa de las implementaciones (actuales):
user system total real
sawa2 0.250000 0.000000 0.250000 ( 0.269027)
phrogz2 0.280000 0.000000 0.280000 ( 0.275027)
sawa1 0.297000 0.000000 0.297000 ( 0.293029)
phrogz3 0.296000 0.000000 0.296000 ( 0.307031)
phrogz1 0.328000 0.000000 0.328000 ( 0.319032)
activesupport 0.639000 0.000000 0.639000 ( 0.657066)
mladen 1.716000 0.000000 1.716000 ( 1.725172)
La segunda respuesta de @sawa es la más rápida de todas, un pelo frente a mi implementación basada en tap
(basada en su primera respuesta). ¿Elegir agregar el cheque para has_key?
agrega muy poco tiempo, y sigue siendo más del doble de rápido que ActiveSupport.
Aquí está el código de referencia:
h1 = Hash[ (''a''..''z'').zip(1..26) ]
keys = %w[a z c d g A x]
n = 60000
require ''benchmark''
Benchmark.bmbm do |x|
%w[ sawa2 phrogz2 sawa1 phrogz3 phrogz1 activesupport mladen ].each do |m|
x.report(m){ n.times{ h1.send(m,*keys) } }
end
end
Cambié por la mente. El anterior no parece ser bueno.
class Hash
def slice1(*keys)
keys.each_with_object({}){|k, h| h[k] = self[k]}
end
def slice2(*keys)
h = {}
keys.each{|k| h[k] = self[k]}
h
end
end
Quizás
class Hash
def slice *keys
select{|k| keys.member?(k)}
end
end
O simplemente podría copiar el segmento Hash#slice
ActiveSupport, se ve un poco más robusto.
Sequel tiene soporte incorporado para solo seleccionar columnas específicas al actualizar:
product.update_fields(params, [:name, :description])
Eso no hace exactamente lo mismo si: name o: description no está presente en params, sin embargo. Pero suponiendo que esperas que el usuario use tu formulario, eso no debería ser un problema.
Siempre podría expandir update_fields para tomar un hash de opciones con una opción que omitirá el valor si no está presente en el hash. Simplemente no he recibido una solicitud para hacer eso todavía.
Solo usaría el método de división provisto por active_support
require ''active_support/core_ext/hash/slice''
{a: 1, b: 2, c: 3}.slice(:a, :c) # => {a: 1, c: 3}
Por supuesto, asegúrese de actualizar su gemfile:
gem ''active_support''