Ruby-Ersatz für mehrere Zeichenfolgen

75
str = "Hello☺ World☹"

Die erwartete Ausgabe ist:

"Hello:) World:("

Ich kann dies tun: str.gsub("☺", ":)").gsub("☹", ":(")

Gibt es eine andere Möglichkeit, dies in einem einzigen Funktionsaufruf zu tun? Etwas wie:

str.gsub(['s1', 's2'], ['r1', 'r2'])
Sayuj
quelle
1
Gibt es einen Grund, warum Sie das in einem Anruf tun möchten? Ich würde es vorziehen, bei Ihrer ersten Lösung zu bleiben.
Simon Perepelitsa
2
@Semyon: Das Mapping-Tabellenpaar ist groß oder kann zur Laufzeit konfiguriert werden.
Mu ist zu kurz
1
Wenn Sie am Ende eine riesige Zuordnungstabelle haben, sehen Sie im Grunde genommen eine Vorlagensprache. In diesem Fall können Sie es in ein DSL konvertieren und dafür einen Interpreter (oder Compiler) schreiben.
Swanand
Ich hatte erwartet String#tr, den Trick zu machen, aber der Ersatz durch mehrere Zeichen bedeutet, dass ich das nicht verwenden kann.
Andrew Grimm

Antworten:

123

Akzeptiert seit Ruby 1.9.2 String#gsubHash als zweiten Parameter zum Ersetzen durch übereinstimmende Schlüssel. Sie können einen regulären Ausdruck verwenden, um die zu ersetzende Teilzeichenfolge abzugleichen und einen Hash für zu ersetzende Werte zu übergeben.

So was:

'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*')    #=> "h3ll*"
'(0) 123-123.123'.gsub(/[()-,. ]/, '')    #=> "0123123123"

In Ruby 1.8.7 würden Sie dasselbe mit einem Block erreichen:

dict = { 'e' => 3, 'o' => '*' }
'hello'.gsub /[eo]/ do |match|
   dict[match.to_s]
 end #=> "h3ll*"
Naren Sisodiya
quelle
woah! Ich hatte keine Ahnung! tolles Zeug!
Kikito
Cool, wusste nichts davon. Eine Art schönere Version von Perl tr.
Marnen Laibow-Koser
Beachten Sie, dass dies nicht mit dem Aufruf von str.gsub (Schlüssel, Wert) für jedes Element des Hash identisch ist. Wenn etwas mit dem regulären Ausdruck übereinstimmt, aber keinen Eintrag im Hash hat, wird es gelöscht.
Sprachprofi
3
@NarenSisodiya, eigentlich sollte es sein: '(0) 123-123.123'.gsub (/ [() \ - ,.] /,' ') Sie müssen das Escape-Zeichen zu' - 'hinzufügen.
jpbalarini
Ja, diese Zeile dort ist falsch: '(0) 123-123.123'.gsub (/ [() -,.] /,' ') Sie müssen entweder dem Armaturenbrett entkommen oder es nach vorne bewegen.
Greg Blass
40

Richten Sie eine Zuordnungstabelle ein:

map = {'☺' => ':)', '☹' => ':(' }

Erstellen Sie dann einen regulären Ausdruck:

re = Regexp.new(map.keys.map { |x| Regexp.escape(x) }.join('|'))

Und schließlich gsub:

s = str.gsub(re, map)

Wenn Sie in 1.8 Land stecken, dann:

s = str.gsub(re) { |m| map[m] }

Sie benötigen das Regexp.escapedort, falls etwas, das Sie ersetzen möchten, innerhalb eines regulären Ausdrucks eine besondere Bedeutung hat. Oder dank steenslag können Sie Folgendes verwenden:

re = Regexp.union(map.keys)

und das Angebot wird für Sie erledigt.

mu ist zu kurz
quelle
@steenslag: Das ist eine schöne Modifikation.
Mu ist zu kurz
String # gsub akzeptiert Strings als Musterparameter: "Das Muster ist normalerweise ein Regexp. Wenn es als String angegeben wird, werden alle darin enthaltenen Metazeichen für reguläre Ausdrücke wörtlich interpretiert, z. B. '\\ d' entspricht einem Spiel, gefolgt von 'd'. anstelle einer Ziffer. ".
Andrew Grimm
@ Andrew: Ja, aber wir müssen mehrere Zeichenfolgen ersetzen, daher der reguläre Ausdruck.
Mu ist zu kurz
Was ist, wenn die Schlüssel der Karte Regex-Ausdrücke sind? Der Ersatz scheint nicht zu funktionieren
content01
@ content01: Aus dem Kopf, ich denke, Sie müssten in diesem Fall eins nach dem anderen gehen:map.each { |re, v| ... }
mu ist zu kurz
37

Sie könnten so etwas tun:

replacements = [ ["☺", ":)"], ["☹", ":("] ]
replacements.each {|replacement| str.gsub!(replacement[0], replacement[1])}

Es mag eine effizientere Lösung geben, aber dies macht den Code zumindest ein bisschen sauberer

Nathan Manousos
quelle
2
Ist es nicht anzunehmen replacements.each?
DanneManne
4
Dies ist nur komplizierter und langsamer.
Texasbruce
1
Der Rückgabewert für eachist die Sammlung, für die aufgerufen wurde. stackoverflow.com/questions/11596879/…
Nathan Manousos
1
Damit es das Ergebnis replacements.reduce(str){|str,replacement| str.gsub(replacement[0],replacement[1])}
zurückgibt
4
@artm können Sie auch tun replacements.inject(str) { |str, (k,v)| str.gsub(k,v) }und vermeiden, dass Sie tun müssen [0]und [1].
Ben Lings
19

Spät zur Party, aber wenn Sie bestimmte Zeichen durch eines ersetzen möchten, können Sie einen regulären Ausdruck verwenden

string_to_replace.gsub(/_|,| /, '-')

In diesem Beispiel ersetzt gsub die Unterstriche (_), Kommas (,) oder () durch einen Bindestrich (-).

lsaffie
quelle
4
das wäre noch besser so:string_to_replace.gsub(/[_- ]/, '-')
Automatico
5

Eine andere einfache und dennoch leicht zu lesende Methode ist die folgende:

str = '12 ene 2013'
map = {'ene' => 'jan', 'abr'=>'apr', 'dic'=>'dec'}
map.each {|k,v| str.sub!(k,v)}
puts str # '12 jan 2013'
Diego Dorado
quelle
5

Sie können tr auch verwenden, um mehrere Zeichen in einer Zeichenfolge gleichzeitig zu ersetzen.

Ersetzen Sie z. B. "h" durch "m" und "l" durch "t".

"hello".tr("hl", "mt")
 => "metto"

sieht einfach, ordentlich und schneller aus (allerdings kein großer Unterschied) als gsub

puts Benchmark.measure {"hello".tr("hl", "mt") }
  0.000000   0.000000   0.000000 (  0.000007)

puts Benchmark.measure{"hello".gsub(/[hl]/, 'h' => 'm', 'l' => 't') }
  0.000000   0.000000   0.000000 (  0.000021)
YasirAzgar
quelle
1

Nach Narens Antwort oben würde ich mitgehen

tr = {'a' => '1', 'b' => '2', 'z' => '26'}
mystring.gsub(/[#{tr.keys}]/, tr)

So 'zebraazzeebra'.gsub(/[#{tr.keys}]/, tr)kehrt "26e2r112626ee2r1"

gitb
quelle