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

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.


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

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.


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

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


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  

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


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

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


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.


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

Ist das effizient?
Ioquatix
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.