Ändern jedes Werts in einem Hash in Ruby


170

Ich möchte jeden Wert in einem Hash ändern, um '%' vor und nach dem Wert hinzuzufügen

{ :a=>'a' , :b=>'b' }

muss geändert werden auf

{ :a=>'%a%' , :b=>'%b%' }

Was ist der beste Weg, dies zu tun?


1
Bitte klären Sie, ob Sie die ursprünglichen Zeichenfolgenobjekte mutieren möchten, nur das Original mutieren möchten oder nichts mutieren möchten.
Phrogz

1
Dann haben Sie die falsche Antwort akzeptiert. (Keine Beleidigung für @pst beabsichtigt, da ich persönlich auch eine funktionale Programmierung befürworte, anstatt Objekte zu mutieren.)
Phrogz

Aber trotzdem war der Ansatz soo nett
theReverseFlick

Antworten:


178

Wenn Sie möchten, dass die tatsächlichen Zeichenfolgen selbst an Ort und Stelle mutieren (was möglicherweise und wünschenswert andere Verweise auf dieselben Zeichenfolgenobjekte beeinflusst):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

Wenn Sie möchten, dass sich der Hash ändert, die Zeichenfolgen jedoch nicht beeinflusst werden (Sie möchten, dass neue Zeichenfolgen angezeigt werden):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

Wenn Sie einen neuen Hash möchten:

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]

1
@ Andrew Marshall Richtig, danke. Hash.[]Akzeptiert in Ruby 1.8 kein Array von Array-Paaren, erfordert es eine gerade Anzahl direkter Argumente (daher der Splat im Voraus).
Phrogz

2
Tatsächlich wurde Hash. [Key_value_pairs] in 1.8.7 eingeführt, sodass nur Ruby 1.8.6 das Splat & Flatten nicht benötigt.
Marc-André Lafortune

2
@Aupajo Hash#eachgibt sowohl den Schlüssel als auch den Wert für den Block aus. In diesem Fall war mir der Schlüssel egal und ich nannte ihn nichts Nützliches. Variablennamen können mit einem Unterstrich beginnen und tatsächlich nur ein Unterstrich sein. Dies hat keinen Leistungsvorteil, es ist nur eine subtile selbstdokumentierende Notiz, dass ich mit diesem ersten Blockwert nichts mache.
Phrogz

1
Ich denke du meinst my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, muss den Hash aus dem Block zurückgeben
aceofspades

1
Alternativ können Sie auch die Methode each_value verwenden, die etwas einfacher zu verstehen ist als die Verwendung eines Unterstrichs für den nicht verwendeten Schlüsselwert.
Strand McCutchen

265

In Ruby 2.1 und höher können Sie dies tun

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h

5
Danke, war eigentlich das, wonach ich gesucht habe. Ich weiß nicht, warum Sie nicht so gut bewertet sind.
Simperreault

7
Dies ist jedoch sehr langsam und sehr RAM-hungrig. Der Eingabe-Hash wird wiederholt, um einen Zwischensatz verschachtelter Arrays zu erzeugen, die dann in einen neuen Hash konvertiert werden. Wenn Sie die RAM-Spitzenauslastung ignorieren, ist die Laufzeit viel schlechter. Wenn Sie dies in einer anderen Antwort mit den Modify-in-Place-Lösungen vergleichen, werden 2,5 Sekunden gegenüber 1,5 Sekunden über dieselbe Anzahl von Iterationen angezeigt. Da Ruby eine vergleichsweise langsame Sprache ist, ist es sehr sinnvoll, langsame Teile der langsamen Sprache zu vermeiden :-)
Andrew Hodgkinson

2
@AndrewHodgkinson, obwohl ich im Allgemeinen zustimme und nicht befürworte, nicht auf die Laufzeitleistung zu achten, wird das Verfolgen all dieser Leistungsprobleme nicht zu einem Schmerz und widerspricht der "Entwicklerproduktivität zuerst" -Philosophie von Ruby? Ich denke, dies ist weniger ein Kommentar für Sie als vielmehr ein allgemeiner Kommentar zu dem möglichen Paradoxon, zu dem wir mit Ruby kommen.
Elsurudo

4
Das Rätsel ist: Nun, wir geben bereits die Leistung bei unserer Entscheidung auf, sogar Rubin zu verwenden. Welchen Unterschied macht also "dieses andere kleine bisschen"? Es ist ein rutschiger Hang, nicht wahr? Aus Gründen der Lesbarkeit ziehe ich diese Lösung der akzeptierten Antwort vor.
Elsurudo

1
Wenn Sie Ruby 2.4+ verwenden, ist die Verwendung noch einfacher, #transform_values!wie von sschmeck ( stackoverflow.com/a/41508214/6451879 ) hervorgehoben.
Finn

127

Ruby 2.4 führte die Methode ein Hash#transform_values!, die Sie verwenden konnten.

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 

Genau das, wonach ich gesucht habe!
Dolev

4
Natürlich gibt es auch Hash#transform_values(ohne den Knall), der den Empfänger nicht verändert. Ansonsten eine tolle Antwort, danke!
iGEL

1
Dies wird meine Verwendung von :-preduce
iGEL

85

Der beste Weg, um die vorhandenen Hash-Werte zu ändern, ist

hash.update(hash){ |_,v| "%#{v}%" }

Weniger Code und klare Absichten. Auch schneller, da über die zu ändernden Werte hinaus keine neuen Objekte zugewiesen werden.


Nicht genau richtig: Neue Zeichenfolgen werden zugewiesen. Trotzdem eine interessante Lösung, die effektiv ist. +1
Phrogz

@Phrogz guter Punkt; Ich habe die Antwort aktualisiert. Die Wertzuweisung kann im Allgemeinen nicht vermieden werden, da nicht alle Werttransformationen als Mutatoren wie z gsub!.
Sim

3
Wie meine Antwort, aber mit einem anderen Synonym stimme ich zu, dass updatedie Absicht besser vermittelt als merge!. Ich denke, das ist die beste Antwort.

1
Wenn Sie nicht verwenden k, verwenden Sie _stattdessen.
sekrett

28

Ein bisschen besser lesbar, mapes zu einem Array von Einzelelement-Hashes und reducedas mitmerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)

2
Klarer zu bedienenHash[the_hash.map { |key,value| [key, "%#{value}%"] }]
meagar

Dies ist eine äußerst ineffiziente Methode zum Aktualisieren von Werten. Für jedes Wertepaar wird zuerst ein Paar Array(für map) und dann a erstellt Hash. In jedem Schritt der Reduzierungsoperation wird dann das "Memo" dupliziert Hashund das neue Schlüssel-Wert-Paar hinzugefügt. Verwenden Sie mindestens :merge!in reduce, um das endgültige Finale zu ändern Hash. Und am Ende ändern Sie nicht die Werte des vorhandenen Objekts, sondern erstellen ein neues Objekt, was in der Frage nicht gestellt wurde.
Sim

es gibt zurück, nilwenn the_hashes leer ist
DNNX


16

Eine Methode, die dem Original keine Nebenwirkungen verleiht:

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

Die Hash # -Karte kann auch eine interessante Lektüre sein, da sie erklärt, warum die Hash.mapkeine Hash zurückgibt (weshalb das resultierende Array von [key,value]Paaren in eine neue Hash konvertiert wird) und alternative Ansätze für dasselbe allgemeine Muster bietet.

Viel Spaß beim Codieren.

[Haftungsausschluss: Ich bin nicht sicher, ob sich die Hash.mapSemantik in Ruby 2.x ändert]


7
Weiß Matz überhaupt, ob sich die Hash.mapSemantik in Ruby 2.x ändert?
Andrew Grimm

1
Die Hash # [] -Methode ist so nützlich, aber so hässlich. Gibt es eine schönere Methode, um Arrays auf die gleiche Weise in Hashes umzuwandeln?
Martijn

15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end

Ich mag keine Nebenwirkungen, aber +1 für den Ansatz :) Es gibt each_with_objectin Ruby 1.9 (IIRC), das den direkten Zugriff auf den Namen vermeidet und Map#mergemöglicherweise auch funktioniert. Ich bin mir nicht sicher, wie sich die komplizierten Details unterscheiden.

1
Der anfängliche Hash wird geändert - dies ist in Ordnung, wenn das Verhalten erwartet wird, kann jedoch subtile Probleme verursachen, wenn es "vergessen" wird. Ich ziehe es vor, die Veränderlichkeit von Objekten zu reduzieren, aber es ist möglicherweise nicht immer praktisch. (Ruby ist kaum eine "

8

Hash.merge! ist die sauberste Lösung

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }

@MichieldeMare Es tut mir leid, ich habe das nicht gründlich genug getestet. Sie haben Recht, der Block muss zwei Parameter annehmen. Korrigiert.

5

Nach dem Testen mit RSpec wie folgt:

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

Sie können Hash # map_values ​​wie folgt implementieren:

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

Die Funktion kann dann folgendermaßen verwendet werden:

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}

1

Wenn Sie neugierig sind, welche Inplace-Variante hier die schnellste ist, ist sie:

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
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.