Ruby Regexp-Gruppenabgleich, weisen Sie Variablen in einer Zeile zu

125

Ich versuche gerade, einen String in mehrere Variablen umzuwandeln. Beispielzeichenfolge:

ryan_string = "RyanOnRails: This is a test"

Ich habe es mit diesem regulären Ausdruck mit 3 Gruppen abgeglichen:

ryan_group = ryan_string.scan(/(^.*)(:)(.*)/i)

Um nun auf jede Gruppe zuzugreifen, muss ich Folgendes tun:

ryan_group[0][0] (first group) RyanOnRails
ryan_group[0][1] (second group) :
ryan_group[0][2] (third group) This is a test

Das scheint ziemlich lächerlich und es fühlt sich an, als würde ich etwas falsch machen. Ich würde erwarten, dass ich so etwas tun kann:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)

Ist das möglich? Oder gibt es einen besseren Weg als wie ich es mache?

Ryanjones
quelle

Antworten:

199

Das willst du nicht scan, da es wenig Sinn macht. Sie können verwenden String#match, um ein MatchDataObjekt zurückzugeben, und dann aufrufen #captures, um ein Array von Captures zurückzugeben. Etwas wie das:

#!/usr/bin/env ruby

string = "RyanOnRails: This is a test"
one, two, three = string.match(/(^.*)(:)(.*)/i).captures

p one   #=> "RyanOnRails"
p two   #=> ":"
p three #=> " This is a test"

Beachten Sie, dass, wenn keine Übereinstimmung gefunden String#matchwird, null zurückgegeben wird, sodass so etwas möglicherweise besser funktioniert:

if match = string.match(/(^.*)(:)(.*)/i)
  one, two, three = match.captures
end

Obwohl scandas wenig Sinn macht. Es erledigt immer noch die Arbeit, Sie müssen nur zuerst das zurückgegebene Array reduzieren.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten

Lee Jarvis
quelle
6
Beachten Sie, dass wenn keine Übereinstimmungen gefunden werden, die Übereinstimmung null zurückgibt und Sie einen NilError erhalten. Wenn Sie in Rails sind, empfehle ich Ihnen, zu ändern: one, two, three = string.match(/(^.*)(:)(.*)/i).captures in: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti
5
@AndreaSalicetti Ich habe meinen Beitrag bearbeitet, ich füge keinen Rails-spezifischen Code hinzu, daher habe ich ihn mit einer Version für die Behandlung des zurückgegebenen Null-Objekts geändert
Lee Jarvis
3
Sie können auch den neuen &.Operator verwenden, um es wieder in eine Zeile zu bringen und es sogar zweimal zu verwenden, wenn es nur eine Erfassungsgruppe gibt. ZB ..,string.match(regex)&.captures&.first
Gerry Shaw
46

Sie könnten Match verwenden stattdessen oder = ~ verwenden, um eine einzelne Übereinstimmung zu erhalten, und Sie können entweder auf die gleiche Weise auf die Übereinstimmungsdaten zugreifen oder einfach die speziellen Übereinstimmungsvariablen $ 1, $ 2, $ 3 verwenden

Etwas wie:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end
Rado
quelle
5
@ Gaston, das ist eigentlich die ursprüngliche Regexp-Syntax von Perl :)
Ohaleck
28

Sie können Ihre erfassten Übereinstimmungen benennen

string = "RyanOnRails: This is a test"
/(?<one>^.*)(?<two>:)(?<three>.*)/i =~ string
puts one, two, three

Es funktioniert nicht, wenn Sie die Reihenfolge von Zeichenfolge und Regex umkehren.

zu senden
quelle
6

Sie müssen entscheiden, ob es eine gute Idee ist, aber Ruby Regexp kann (automatisch) lokale Variablen definieren für Sie definieren!

Ich bin mir noch nicht sicher, ob diese Funktion fantastisch oder einfach nur verrückt ist, aber Ihre Regex kann lokale Variablen definieren.

ryan_string = "RyanOnRails: This is a test"
/^(?<webframework>.*)(?<colon>:)(?<rest>)/ =~ ryan_string
# This defined three variables for you. Crazy, but true.
webframework # => "RyanOnRails"
puts "W: #{webframework} , C: #{colon}, R: #{rest}"

(Schauen Sie unter http://ruby-doc.org/core-2.1.1/Regexp.html nach "lokale Variable".)

Hinweis: Wie in einem Kommentar erwähnt, gibt es eine ähnliche und frühere Antwort auf diese Frage von @toonsend ( https://stackoverflow.com/a/21412455 ). Ich glaube nicht, dass ich "gestohlen" habe, aber wenn Sie fair mit Lob sein und die erste Antwort ehren wollen, fühlen Sie sich frei :) Ich hoffe, dass keine Tiere verletzt wurden.

Felix
quelle
Diese Antwort sieht bemerkenswert ähnlich zu stackoverflow.com/a/21412455/525478 aus , das über ein Jahr älter ist ...
Brad Werth
@BradWerth Das habe ich wohl einfach nicht gesehen. Aber ich habe meine Antwort aktualisiert, um Ihre Bedenken aufzunehmen.
Felix
5

scan() findet alle nicht überlappenden Übereinstimmungen des regulären Ausdrucks in Ihrer Zeichenfolge. Anstatt also ein Array Ihrer Gruppen zurückzugeben, wie Sie es zu erwarten scheinen, gibt es ein Array von Arrays zurück.

Sie sind wahrscheinlich besser dran match(), wenn Sie das Array der Captures verwenden, indem Sie Folgendes verwenden MatchData#captures:

g1, g2, g3 = ryan_string.match(/(^.*)(:)(.*)/i).captures

Sie können dies jedoch auch tun, scan()wenn Sie möchten:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
Andrew Clark
quelle