Cambiar cada valor en un hash en Ruby

170

Quiero cambiar cada valor en un hash para agregar '%' antes y después del valor para

{ :a=>'a' , :b=>'b' }

debe cambiarse a

{ :a=>'%a%' , :b=>'%b%' }

¿Cuál es la mejor manera de hacer esto?

theReverseFlick
fuente
1
Por favor, aclare si desea mutar los objetos de cadena originales, simplemente mute el original o mute nada.
Phrogz
1
Entonces has aceptado la respuesta incorrecta. (Sin ofender a @pst, ya que personalmente también defiendo la programación de estilo funcional en lugar de objetos mutantes)
Phrogz
Pero aún así, el enfoque fue agradable
revés Flick

Respuestas:

178

Si desea que las cadenas en sí mismas muten en su lugar (posiblemente y deseablemente afecten otras referencias a los mismos objetos de cadena):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

Si desea que el hash cambie en su lugar, pero no desea afectar las cadenas (desea que obtenga nuevas cadenas):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

Si quieres un nuevo hash:

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]
Phrogz
fuente
1
@ Andrew Marshall Tienes razón, gracias. En Ruby 1.8, Hash.[]no acepta una matriz de pares de matrices, requiere un número par de argumentos directos (de ahí la splat por adelantado).
Phrogz
2
En realidad, Hash. [Key_value_pairs] se introdujo en 1.8.7, por lo que solo Ruby 1.8.6 no necesita splat & flatten.
Marc-André Lafortune
2
@Aupajo Hash#eachproduce tanto la clave como el valor para el bloque. En este caso, no me importaba la clave, por lo que no le puse nada útil. Los nombres de las variables pueden comenzar con un guión bajo y, de hecho, pueden ser solo un guión bajo. No hay ningún beneficio de rendimiento al hacer esto, es solo una sutil nota de autodocumentación de que no estoy haciendo nada con ese primer valor de bloque.
Phrogz
1
Creo que quieres decir my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, tengo que devolver el hash del bloque
aceofspades
1
Alternativamente, puede usar el método each_value, que es un poco más fácil de entender que usar un guión bajo para el valor clave no utilizado.
Strand McCutchen
266

En Ruby 2.1 y superior puedes hacer

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
shock_one
fuente
55
Gracias, en realidad era lo que estaba buscando. No sé por qué no te votan tanto.
simperreault
77
Sin embargo, esto es muy lento y tiene mucha RAM. El hash de entrada se repite para producir un conjunto intermedio de matrices anidadas que luego se convierten en un nuevo hash. Ignorando el uso máximo de RAM, el tiempo de ejecución es mucho peor: comparar esto con las soluciones de modificación en el lugar en otra respuesta muestra 2.5s versus 1.5s en el mismo número de iteraciones. Dado que Ruby es un lenguaje relativamente lento, evitar los fragmentos lentos del lenguaje lento tiene mucho sentido :-)
Andrew Hodgkinson
2
@AndrewHodgkinson, si bien, en general, estoy de acuerdo y no estoy abogando por no prestar atención al rendimiento en tiempo de ejecución, ¿no hace un seguimiento de todos estos escollos de rendimiento que comienza a convertirse en una molestia y va en contra de la filosofía de ruby ​​"productividad del desarrollador primero"? Supongo que esto es menos un comentario para ti, y más un comentario general sobre la eventual paradoja a la que nos lleva, usando ruby.
elsurudo
44
El enigma es: bueno, ya estamos renunciando al rendimiento en nuestra decisión de incluso usar rubí, entonces, ¿qué diferencia hace "este otro poquito"? Es una pendiente resbaladiza, ¿no? Para el registro, prefiero esta solución a la respuesta aceptada, desde una perspectiva de legibilidad.
elsurudo
1
Si usa Ruby 2.4+, es aún más fácil de usar #transform_values!según lo indicado por sschmeck ( stackoverflow.com/a/41508214/6451879 ).
Finn el
128

Ruby 2.4 introdujo el método Hash#transform_values!, que puedes usar.

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 
sschmeck
fuente
¡Exactamente lo que estaba buscando!
Dolev
44
Por supuesto, también existe Hash#transform_values(sin la explosión), que no modifica el receptor. De lo contrario, una gran respuesta, gracias!
iGEL
1
Esto realmente reducirá mi uso de reduce:-p
iGEL
86

La mejor manera de modificar los valores de un Hash en su lugar es

hash.update(hash){ |_,v| "%#{v}%" }

Menos código e intención clara. También más rápido porque no se asignan objetos nuevos más allá de los valores que se deben cambiar.

Sim
fuente
No es exactamente cierto: se asignan nuevas cadenas. Aún así, una solución interesante que es efectiva. +1
Phrogz
@Phrogz buen punto; Actualicé la respuesta. La asignación de valores no se puede evitar en general porque no todas las transformaciones de valores se pueden expresar como mutadores como gsub!.
Sim
3
Igual que mi respuesta pero con otro sinónimo, estoy de acuerdo en que updatetransmite la intención mejor que merge!. Creo que esta es la mejor respuesta.
1
Si no usa k, use _en su lugar.
sekrett
28

Un poco más legible, mapa una matriz de hashes de un solo elemento y reduceeso conmerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
edgerunner
fuente
2
Más Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
fácil de
Esta es una forma extremadamente ineficiente de actualizar valores. Para cada par de valores, primero crea un par Array(para map) y luego a Hash. Luego, cada paso de la operación de reducción duplicará el "memo" Hashy le agregará el nuevo par clave-valor. Al menos uso :merge!en reducemodificar la final Hashen su lugar. Y al final, no está modificando los valores del objeto existente, sino que está creando un nuevo objeto, que no es lo que hizo la pregunta.
Sim
vuelve nilsi the_hashestá vacío
DNNX
16

Un método que no introduce efectos secundarios al original:

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

El mapa Hash # también puede ser una lectura interesante, ya que explica por qué el Hash.mapno devuelve un Hash (razón por la cual la matriz resultante de[key,value] pares se convierte en un nuevo Hash) y proporciona enfoques alternativos para el mismo patrón general.

Feliz codificación

[Descargo de responsabilidad: no estoy seguro si la Hash.mapsemántica cambia en Ruby 2.x]


fuente
77
¿Matz sabe siquiera si la Hash.mapsemántica cambia en Ruby 2.x?
Andrew Grimm
1
El método Hash # [] es muy útil, pero muy feo. ¿Existe un método más bonito para convertir matrices a hashes de la misma manera?
Martijn
15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end
Andrew Marshall
fuente
No me gustan los efectos secundarios, pero +1 para el enfoque :) Existe each_with_objecten Ruby 1.9 (IIRC) que evita tener que acceder al nombre directamente y Map#mergetambién puede funcionar. No estoy seguro de cómo difieren los detalles intrincados.
1
El hash inicial se modifica; esto está bien si se anticipa el comportamiento, pero puede causar problemas sutiles si se "olvida". Prefiero reducir la mutabilidad del objeto, pero puede no ser siempre práctico. (Ruby no es un lenguaje "sin efectos secundarios" ;-)
8

Hash.merge! es la solución más limpia

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }
dorio
fuente
@MichieldeMare Lo siento, no probé esto lo suficientemente a fondo. Tienes razón, el bloque necesita tomar dos parámetros. Corregido
5

Después de probarlo con RSpec así:

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

Puede implementar Hash # map_values ​​de la siguiente manera:

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

La función se puede usar así:

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}
wedesoft
fuente
1

Si tiene curiosidad por saber qué variante in situ es la más rápida, aquí está:

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
lzap
fuente