¿Cómo cambiar los valores Hash?


126

Me gustaría reemplazar cada uno valueen un hash con value.some_method.

Por ejemplo, para un hash simple dado:

{"a" => "b", "c" => "d"}` 

cada valor debe ser .upcased, por lo que se ve así:

{"a" => "B", "c" => "D"}

Lo intenté #collecty #mapsiempre obtengo los arreglos. ¿Hay alguna forma elegante de hacer esto?

ACTUALIZAR

Maldición, lo olvidé: el hash está en una variable de instancia que no debe cambiarse. Necesito un nuevo hash con los valores modificados, pero preferiría no definir esa variable explícitamente y luego recorrer el hash llenándolo. Algo como:

new_hash = hash.magic{ ... }

El segundo ejemplo en mi respuesta devuelve un nuevo hash. coméntalo si quieres que amplíe la magia que está haciendo #inject.
kch

Respuestas:


218
my_hash.each { |k, v| my_hash[k] = v.upcase } 

o, si prefiere hacerlo de forma no destructiva, y devolver un nuevo hash en lugar de modificarlo my_hash:

a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h } 

Esta última versión tiene el beneficio adicional de que también podría transformar las claves.


26
La primera sugerencia no es apropiada; modificar un elemento enumerable mientras se itera conduce a un comportamiento indefinido; esto también es válido en otros idiomas. Aquí hay una respuesta de Matz (él responde a un ejemplo usando una matriz, pero la respuesta es genérica): j.mp/tPOh2U
Marcus

44
@Marcus Estoy de acuerdo en que, en general, dicha modificación debe evitarse. Pero en este caso probablemente sea seguro, porque la modificación no cambia las iteraciones futuras. Ahora, si se asignó otra clave (que no fue la que se pasó al bloque), entonces habría problemas.
Kelvin

36
@Adam @kch each_with_objectes una versión más conveniente de injectcuando no quieres terminar el bloque con el hash:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin

66
También hay una sintaxis muy clara para convertir una lista de pares en un hash, para que pueda escribira_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
reescrita el

2
con Ruby 2 puedes escribir.to_h
roman-roman


35

Puede recopilar los valores y convertirlos de Array a Hash nuevamente.

Me gusta esto:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]

23

Esto lo hará:

my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase }

En contraposición a injectla ventaja es que no es necesario devolver el hash nuevamente dentro del bloque.


Me gusta que este método no modifique el hash original.
Christian Di Lorenzo

10

Prueba esta función:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.

55
eso es bueno, pero debe tenerse en cuenta que solo funciona cuando j tiene un método destructivo que actúa en sí mismo. Por lo tanto, no puede manejar el caso general value.some_method.
kch

Buen punto, y con su actualización, su segunda solución (no destructiva) es la mejor.
Chris Doggett

10

Hay un método para eso en ActiveSupport v4.2.0. Se llamatransform_values y básicamente solo ejecuta un bloque para cada par clave-valor.

Como lo están haciendo con un each, creo que no hay mejor manera que recorrerlo.

hash = {sample: 'gach'}

result = {}
hash.each do |key, value|
  result[key] = do_stuff(value)
end

Actualizar:

Desde Ruby 2.4.0 puedes usar #transform_valuesy #transform_values!.


2

Es posible que desee ir un paso más allá y hacerlo en un hash anidado. Ciertamente, esto sucede bastante con los proyectos de Rails.

Aquí hay un código para garantizar que un hash de params esté en UTF-8:

  def convert_hash hash
    hash.inject({}) do |h,(k,v)|
      if v.kind_of? String
        h[k] = to_utf8(v) 
      else
        h[k] = convert_hash(v)
      end
      h
    end      
  end    

  # Iconv UTF-8 helper
  # Converts strings into valid UTF-8
  #
  # @param [String] untrusted_string the string to convert to UTF-8
  # @return [String] your string in UTF-8
  def to_utf8 untrusted_string=""
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
    ic.iconv(untrusted_string + ' ')[0..-2]
  end  

1

Hago algo como esto:

new_hash = Hash [* original_hash.collect {| clave, valor | [clave, valor + 1]}. aplanar]

Esto le proporciona las facilidades para transformar la clave o el valor a través de cualquier expresión también (y no es destructivo, por supuesto).


1

Ruby tiene el tapmétodo ( 1.8.7 , 1.9.3 y 2.1.0 ) que es muy útil para cosas como esta.

original_hash = { :a => 'a', :b => 'b' }
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } }
# => {:a=>"A", :b=>"B"}
original_hash # => {:a=>"a", :b=>"b"}

1

Carriles específicos

En caso de que alguien solo necesite llamar al to_smétodo a cada uno de los valores y no esté usando Rails 4.2 (que incluye el enlace deltransform_values método ), puede hacer lo siguiente:

original_hash = { :a => 'a', :b => BigDecimal('23.4') }
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>}
JSON(original_hash.to_json)
#=> {"a"=>"a", "b"=>"23.4"}

Nota: Se requiere el uso de la biblioteca 'json'.

Nota 2: Esto también convertirá las teclas en cadenas


1

Si sabe que los valores son cadenas, puede llamar al reemplazo método de en ellos durante el ciclo: de esta manera cambiará el valor.

Altohugh, esto se limita al caso en el que los valores son cadenas y, por lo tanto, no responde la pregunta por completo, pensé que podría ser útil para alguien.


1
new_hash = old_hash.merge(old_hash) do |_key, value, _value|
             value.upcase
           end

# old_hash = {"a" => "b", "c" => "d"}
# new_hash = {"a" => "B", "c" => "D"}

¿Es esto eficiente?
ioquatix
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.