Wie ändere ich Hash-Werte?

126

Ich möchte jeden valuein einem Hash durch ersetzen value.some_method.

Zum Beispiel für einen einfachen Hash:

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

Jeder Wert sollte .upcased sein, also sieht es so aus:

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

Ich habe es versucht #collectund #mapbekomme immer nur Arrays zurück. Gibt es eine elegante Möglichkeit, dies zu tun?

AKTUALISIEREN

Verdammt, ich habe vergessen: Der Hash befindet sich in einer Instanzvariablen, die nicht geändert werden sollte. Ich benötige einen neuen Hash mit den geänderten Werten, möchte diese Variable jedoch nicht explizit definieren und dann den durchlaufenden Hash durchlaufen. Etwas wie:

new_hash = hash.magic{ ... }
Kali
quelle
Das zweite Beispiel in meiner Antwort gibt einen neuen Hash zurück. Kommentieren Sie es, wenn Sie möchten, dass ich die Magie von #inject erläutere.
kch

Antworten:

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

oder, wenn Sie es lieber zerstörungsfrei machen und einen neuen Hash zurückgeben möchten, anstatt ihn zu ändern my_hash:

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

Diese letzte Version hat den zusätzlichen Vorteil, dass Sie auch die Schlüssel transformieren können.

kch
quelle
26
Der erste Vorschlag ist nicht angemessen; Das Ändern eines aufzählbaren Elements während des Iterierens führt zu undefiniertem Verhalten - dies gilt auch in anderen Sprachen. Hier ist eine Antwort von Matz (er antwortet auf ein Beispiel mit einem Array, aber die Antwort ist generisch): j.mp/tPOh2U
Marcus
4
@Marcus Ich stimme zu, dass solche Änderungen im Allgemeinen vermieden werden sollten. Aber in diesem Fall ist es wahrscheinlich sicher, da die Änderung zukünftige Iterationen nicht ändert. Wenn nun ein anderer Schlüssel (der nicht an den Block übergeben wurde) zugewiesen wurde, würde es Probleme geben.
Kelvin
36
@Adam @kch each_with_objectist eine bequemere Version, injectwenn Sie den Block nicht mit dem Hash beenden möchten:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin
6
Es gibt auch eine sehr nette Syntax, um eine Liste von Paaren in einen Hash umzuwandeln, so dass Sie schreiben könnena_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
umgeschrieben am
2
mit Ruby 2 können Sie schreiben.to_h
Roman-Roman
35

Sie können die Werte sammeln und erneut von Array in Hash konvertieren.

So was:

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

Dies wird es tun:

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

Im Gegensatz zum injectVorteil ist, dass Sie den Hash nicht erneut innerhalb des Blocks zurückgeben müssen.

Konrad Reiche
quelle
Ich mag, dass diese Methode den ursprünglichen Hash nicht ändert.
Christian Di Lorenzo
10

Versuchen Sie diese Funktion:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.
Chris Doggett
quelle
5
Das ist gut, aber es sollte beachtet werden, dass es nur funktioniert, wenn j eine destruktive Methode hat, die auf sich selbst wirkt. Daher kann der allgemeine Fall value.some_method nicht behandelt werden.
kch
Guter Punkt, und mit seinem Update ist Ihre zweite Lösung (zerstörungsfrei) die beste.
Chris Doggett
10

Dafür gibt es in ActiveSupport v4.2.0 eine Methode . Es wird aufgerufen transform_valuesund führt im Grunde nur einen Block für jedes Schlüssel-Wert-Paar aus.

Da sie es mit einem machen each, gibt es meiner Meinung nach keinen besseren Weg, als sich durchzuschleifen.

hash = {sample: 'gach'}

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

Aktualisieren:

Seit 2.4.0 Rubin können Sie nativ nutzen #transform_valuesund #transform_values!.

schmijos
quelle
2

Möglicherweise möchten Sie noch einen Schritt weiter gehen und dies für einen verschachtelten Hash tun. Sicherlich passiert dies bei Rails-Projekten ziemlich oft.

Hier ist ein Code, um sicherzustellen, dass ein Parameter-Hash in UTF-8 enthalten ist:

  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
quelle
1

Ich mache so etwas:

new_hash = Hash [* original_hash.collect {| Schlüssel, Wert | [Schlüssel, Wert + 1]}. abflachen]

Dies bietet Ihnen die Möglichkeit, den Schlüssel oder Wert auch über einen beliebigen Ausdruck zu transformieren (und dies ist natürlich zerstörungsfrei).


quelle
1

Ruby hat die tapMethode ( 1.8.7 , 1.9.3 und 2.1.0 ), die für solche Dinge sehr nützlich ist.

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
quelle
1

Schienen-spezifisch

Falls jemand muss nur anrufen to_sMethode der Werte für jeden und ist mit Rails nicht 4.2 (einschließlich transform_valuesVerfahren Link ), können Sie folgendes tun:

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

Hinweis: Die Verwendung der 'json'-Bibliothek ist erforderlich.

Hinweis 2: Dadurch werden Schlüssel auch in Zeichenfolgen umgewandelt

lllllll
quelle
1

Wenn Sie wissen, dass die Werte Zeichenfolgen sind, können Sie im Zyklus die Ersetzungsmethode für sie aufrufen : Auf diese Weise ändern Sie den Wert.

Obwohl dies auf den Fall beschränkt ist, in dem die Werte Zeichenfolgen sind und daher die Frage nicht vollständig beantworten, dachte ich, dass es für jemanden nützlich sein kann.

wirklich nett
quelle
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
quelle
Ist das effizient?
Ioquatix