¿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{ ... }
potasa
fuente
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.

kch
fuente
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] } ]
Endel
fuente
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.

Konrad Reiche
fuente
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"}.
Chris Doggett
fuente
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!.

schmijos
fuente
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  
Tokumine
fuente
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).


fuente
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"}
Nate
fuente
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

lllllll
fuente
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.

muy agradable
fuente
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"}
cnst
fuente
¿Es esto eficiente?
ioquatix