Comment changer les valeurs de hachage?

126

Je voudrais remplacer chacun valuedans un hachage par value.some_method.

Par exemple, pour un hachage simple:

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

chaque valeur doit être .upcased, donc cela ressemble à:

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

J'ai essayé #collectet #mapmais toujours juste récupérer des tableaux. Y a-t-il une manière élégante de faire cela?

METTRE À JOUR

Merde, j'ai oublié: le hachage est dans une variable d'instance qui ne devrait pas être modifiée. J'ai besoin d'un nouveau hachage avec les valeurs modifiées, mais je préférerais ne pas définir cette variable explicitement, puis boucler sur le hachage en le remplissant. Quelque chose comme:

new_hash = hash.magic{ ... }
potashine
la source
le deuxième exemple de ma réponse renvoie un nouveau hachage. commentez-le si vous voulez que je développe la magie que fait #inject.
kch

Réponses:

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

ou, si vous préférez le faire de manière non destructive, et renvoyer un nouveau hachage au lieu de modifier my_hash:

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

Cette dernière version a l'avantage supplémentaire de pouvoir également transformer les clés.

kch
la source
26
La première suggestion n'est pas appropriée; la modification d'un élément énumérable pendant l'itération conduit à un comportement indéfini - ceci est également valable dans d'autres langues. Voici une réponse de Matz (il répond à un exemple utilisant un tableau, mais la réponse est générique): j.mp/tPOh2U
Marcus
4
@Marcus Je suis d'accord qu'en général, une telle modification doit être évitée. Mais dans ce cas, c'est probablement sûr, car la modification ne change pas les futures itérations. Maintenant, si une autre clé (qui n'était pas celle passée au bloc) était attribuée, alors il y aurait des problèmes.
Kelvin
36
@Adam @kch each_with_objectest une version plus pratique de injectquand vous ne voulez pas terminer le bloc avec le hachage:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin
6
Il existe également une syntaxe très soignée pour convertir une liste de paires en hachage, afin que vous puissiez écrirea_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
réécrit
2
avec Ruby 2 vous pouvez écrire.to_h
roman-roman
35

Vous pouvez collecter les valeurs et les convertir à nouveau de Array en Hash.

Comme ça:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]
Endel
la source
23

Cela le fera:

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

Par opposition à injectl'avantage, vous n'avez pas besoin de renvoyer le hachage à l'intérieur du bloc.

Konrad Reiche
la source
J'aime que cette méthode ne modifie pas le hachage d'origine.
Christian Di Lorenzo le
10

Essayez cette fonction:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.
Chris Doggett
la source
5
c'est bien, mais il faut noter que cela ne fonctionne que lorsque j a une méthode destructrice qui agit d'elle-même. Il ne peut donc pas gérer le cas général value.some_method.
kch
Bon point, et avec sa mise à jour, votre deuxième solution (non destructive) est la meilleure.
Chris Doggett
10

Il existe une méthode pour cela dans ActiveSupport v4.2.0. Il est appelé transform_valueset exécute simplement un bloc pour chaque paire clé-valeur.

Puisqu'ils le font avec un, eachje pense qu'il n'y a pas de meilleur moyen que de passer en boucle.

hash = {sample: 'gach'}

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

Mettre à jour:

Depuis Ruby 2.4.0, vous pouvez utiliser nativement #transform_valueset #transform_values!.

schmijos
la source
2

Vous pouvez aller plus loin et le faire sur un hachage imbriqué. Cela se produit certainement beaucoup avec les projets Rails.

Voici du code pour vous assurer qu'un hachage de paramètres 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
la source
1

Je fais quelque chose comme ça:

new_hash = Hash [* original_hash.collect {| clé, valeur | [clé, valeur + 1]}. flatten]

Cela vous offre également la possibilité de transformer la clé ou la valeur via n'importe quelle expression (et ce n'est pas destructif, bien sûr).


la source
1

Ruby a la tapméthode ( 1.8.7 , 1.9.3 et 2.1.0 ) qui est très utile pour des choses comme cela.

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
la source
1

Spécifique aux rails

to_sSi quelqu'un a seulement besoin d'appeler la méthode pour chacune des valeurs et n'utilise pas Rails 4.2 (qui inclut le lien detransform_values méthode ), vous pouvez faire ce qui suit:

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

Remarque: l'utilisation de la bibliothèque 'json' est requise.

Remarque 2: Cela transformera également les clés en chaînes

lllllll
la source
1

Si vous savez que les valeurs sont des chaînes, vous pouvez appeler la méthode replace sur celles-ci pendant le cycle: de cette façon, vous changerez la valeur.

Altohugh cela est limité au cas où les valeurs sont des chaînes et ne répondent donc pas complètement à la question, j'ai pensé que cela pouvait être utile à quelqu'un.

vraiment sympa
la source
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
la source
Est-ce efficace?
ioquatix