2010-06-22 6 views
27

Ich bin ein Neuling zum Rubin, ich möchte wissen, ob ich nur eine Linie benutzen kann, um den Job zu tun.Wie man einen regulären Ausdruck der Linie benutzt, um übereinstimmenden Inhalt zu erhalten

Nehmen Sie die "Suche" dieser Website zum Beispiel. Wenn der Benutzer [ruby] regex eingegeben haben, kann ich folgenden Code verwenden, das Tag und Keyword

'[ruby] regex' =~ /\[(.*?)\](.*)/ 
tag, keyword = $1, $2 

Können wir es in einer Zeile nur schreiben zu bekommen?


UPDATE

Vielen Dank! Darf ich es schwieriger und interessanter zu machen, dass der Eingang kann mehr als eine Tags enthält, wie:

[ruby] [regex] [rails] one line 

Ist es möglich, eine Zeile Code zu verwenden, um die Tags Array und das Stichwort zu bekommen? Ich habe es versucht, aber gescheitert.

+1

Für das Update: Wenn Sie dies in einem einzigen regulären Ausdruck tun möchten, benötigen Sie die .NET oder Perl 6 Regex-Engine, derzeit die einzigen, die Captures innerhalb wiederholter Elemente unterstützen. Mit IronRuby haben Sie wahrscheinlich eine Chance. Siehe auch http://stackoverflow.com/questions/2652554/which-regex-flavors-support-captures-aso-opped-to-capturing-groups - aus Gründen der Lesbarkeit und Wartbarkeit ist jedoch ein zweistufiger Ansatz wahrscheinlich sinnvoller . –

Antwort

41

Sie benötigen die Regexp#match Methode. Wenn Sie /\[(.*?)\](.*)/.match('[ruby] regex') schreiben, wird ein Objekt MatchData zurückgegeben. Wenn wir das Objekt matches nennen, dann unter anderem:

  • matches[0] gibt die ganze abgestimmte String.
  • matches[n] gibt die n-te Erfassungsgruppe zurück ($n).
  • matches.to_a gibt ein Array bestehend aus matches[0] bis matches[N] zurück.
  • matches.captures gibt ein Array zurück, das nur aus der einfangenden Gruppe besteht (matches[1] bis matches[N]).
  • matches.pre_match gibt alles vor der übereinstimmenden Zeichenfolge zurück.
  • matches.post_match gibt alles nach der übereinstimmenden Zeichenfolge zurück.

Es gibt mehr Methoden, die anderen speziellen Variablen entsprechen, usw .; Sie können überprüfen, MatchData's docs für mehr. Somit wird in diesem speziellen Fall alles, was Sie schreiben müssen, ist

tag, keyword = /\[(.*?)\](.*)/.match('[ruby] regex').captures 

Edit 1: Okay, für die schwierigere Aufgabe, wirst du stattdessen die String#scan Methode wollen, die @Theo verwendet; Wir werden jedoch eine andere Regex verwenden. Der folgende Code sollte funktionieren:

# You could inline the regex, but comments would probably be nice. 
tag_and_text =/\[([^\]]*)\] # Match a bracket-delimited tag, 
       \s*   # ignore spaces, 
       ([^\[]*) /x # and match non-tag search text. 
input  = '[ruby] [regex] [rails] one line [foo] [bar] baz' 
tags, texts = input.scan(tag_and_text).transpose 

Die input.scan(tag_and_text) wird eine Liste von Tag-Suche Textpaare zurück:

[ ["ruby", ""], ["regex", ""], ["rails", "one line "] 
, ["foo", ""], ["bar", "baz"] ] 

Der transpose Anruf klappt das, so dass Sie ein Paar, bestehend aus einem Tag Liste und eine Suchtextliste:

[["ruby", "regex", "rails", "foo", "bar"], ["", "", "one line ", "", "baz"]] 

Sie können dann tun, was Sie wollen, mit den Ergebnissen. Ich könnte darauf hindeuten, zum Beispiel

search_str = texts.join(' ').strip.gsub(/\s+/, ' ') 

Dies wird die Suche Schnipsel mit einzelnen Leerzeichen verketten, loszuwerden führenden und nachfolgenden Leerzeichen und ersetzen Läufe mehrerer Räume mit einem einzigen Raum.

11
'[ruby] regex'.scan(/\[(.*?)\](.*)/) 

wird

[["ruby", " regex"]] 

zurückkehren Sie können mehr über String # scan hier lesen: http://ruby-doc.org/core/classes/String.html#M000812 (kurz, es gibt ein Array aller Spiele in Folge, wobei die äußere Anordnung in diesem Fall ist die Anordnung der Spiele , und das Innere ist die Fanggruppen der einen Übereinstimmung).

die Zuordnung tun Sie es wie folgt neu schreiben kann (vorausgesetzt, Sie werden immer nur ein Spiel in der Kette haben):

tag, keyword = '[ruby] regex'.scan(/\[(.*?)\](.*)/).flatten 

je nach genau das, was Sie erreichen möchten Sie vielleicht die Regex ändern zu

/^\s*\[(.*?)\]\s*(.+)\s*$/ 

, die die gesamte Eingabezeichenfolge übereinstimmt, und schneidet einige Leerzeichen aus der zweiten Erfassungsgruppe. Das Verankern des Musters am Anfang und am Ende macht es ein wenig effizienter und es wird vermieden, in einigen Fällen falsche oder doppelte Übereinstimmungen zu erhalten (aber das hängt sehr von der Eingabe ab) - es garantiert auch, dass Sie das Zurückgegebene sicher verwenden können Array in Zuweisung, weil es nie mehr als eine Übereinstimmung haben wird.

Wie für die Follow-up-Frage, das ist, was ich tun würde:

def tags_and_keyword(input) 
    input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) do |match| 
    tags = match[0].split(/\]\s*\[/) 
    line = match[1] 
    return tags, line 
    end 
end 

tags, keyword = tags_and_keyword('[ruby] [regex] [rails] one line') 
tags # => ["ruby", "regex", "rails"] 
keyword # => "one line" 

es in einer Zeile neu geschrieben werden kann, aber ich würde nicht:

tags, keyword = catch(:match) { input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) { |match| throw :match, [match[0].split(/\]\s*\[/), match[1]] } } 

Meine Lösung übernimmt alle Tags kommen vor dem Schlüsselwort und es gibt nur einen Tag/Schlüsselwort-Ausdruck in jeder Eingabe. Das erste Capture markiert alle Tags, aber dann teile ich diese Zeichenfolge. Es handelt sich also um einen zweistufigen Prozess (der, wie @Tim in seinem Kommentar schrieb, erforderlich ist, es sei denn, Sie verfügen über eine Engine, die für rekursives Matching geeignet ist).

Verwandte Themen