2009-05-01 39 views
96

Me gustaría reemplazar cada value en un hash con value.some_method.¿Cómo cambiar los valores de Hash?

Por ejemplo, para dar un hash simple:

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

cada valor debe ser .upcase d, por lo que parece:

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

Probé #collect y #map pero siempre acaba de obtener matrices de vuelta . ¿Hay una manera elegante de hacer esto?

ACTUALIZACIÓN

Maldición, olvidé: El hash es una variable de instancia en la cual no debe ser cambiado. Necesito un hash nuevo con los valores modificados, pero preferiría no definir esa variable explícitamente y luego pasar por encima del hash que la llena. Algo así como:

new_hash = hash.magic{ ... } 
+0

el segundo ejemplo en mi respuesta devuelve un nuevo hash. comente si quiere que amplíe la magia que #inject está haciendo. – kch

Respuesta

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

o, si se prefiere hacerlo de forma no destructiva, y devolver una nueva almohadilla en lugar de modificar my_hash:

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

Esta última versión tiene el añadido beneficio de que podrías transformar las llaves también.

+22

La primera sugerencia no es apropiada; la modificación de 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): http://j.mp/tPOh2U – Marcus

+4

@Marcus Estoy de acuerdo en que tales modificaciones deberían evitarse. Pero en este caso, probablemente sea seguro, porque la modificación no cambia futuras iteraciones. Ahora bien, si se asignara otra clave (que no era la que se pasó al bloque), entonces habría problemas. – Kelvin

+28

@Adam @kch 'each_with_object' es una versión más conveniente de' inject' cuando no desea finalizar el bloque con el hash: 'my_hash.each_with_object ({}) {| (k, v), h | h [k] = v.upcase} ' – Kelvin

9

Pruebe esta función:

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

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

+0

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

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).

+0

Esto es bastante inteligente también. ¡Gracias!:) –

1

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

Aquí hay un código para garantizar un hash 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 
30

Usted puede obtener los valores, y convertirlo de matriz en Hash de nuevo.

De esta manera:

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

Esto es más conciso y en mi humilde opinión mejor sintácticamente - como @rewritten comentó sobre la respuesta aceptada – MatzFan

21

Esto lo hará:

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

A diferencia de inject la ventaja es que usted está en necesidad de volver el hash de nuevo en el interior del bloque.

+0

Me gusta que este método no modifique el hash original. –

1

Ruby tiene la tap mé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 sólo tiene que llamar to_s método para cada uno de los valores y no está usando Rails 4.2 (que incluye transform_values método link), 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: El uso de la biblioteca 'json' se requier ed.

Nota 2: Esto a su vez claves en cadenas, así

7

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

Dado que lo están haciendo con un each, creo que no hay mejor manera de pasar.

hash = {sample: 'gach'} 

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

Si sabe que los valores son cadenas, puede llamar al método replace en ellos, mientras que en el ciclo: de esta manera va a cambiar el valor.

Altohugh Esto se limita al caso en que los valores son cadenas y por lo tanto no responde la pregunta completamente, 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"} 
+0

¿Es esto eficiente? – ioquatix

28

Desde ruby 2.4.0 puede utilizar nativa Hash#transform_values método:

hash = {"a" => "b", "c" => "d"} 
new_hash = hash.transform_values(&:upcase) 
# => {"a" => "B", "c" => "D"} 

También hay destructiva versión Hash#transform_values!.

+3

genial, también viene con rieles 4.2.1 https://apidock.com/rails/v4.2.1/Hash/transform_values ​​ –

+1

Esta es la mejor solución. El código es más corto y más legible. –

+0

@ZackXu Esto es mejor, pero no nos olvidemos del hecho de que esta pregunta se hizo en 2009 y la mayoría de las respuestas se escribieron en ese momento. Avance rápido hasta 2017, las cosas son mucho más fáciles para la clave Hash y las transformaciones de valores porque las versiones más nuevas de Ruby implementaron esos métodos. – rubyprince

Cuestiones relacionadas