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{ ... }

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.


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

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.


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

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


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  

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


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

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


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.


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

Est-ce efficace?
ioquatix
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.