2011-01-14 7 views
28

Ich habe eine Zeichenfolge:Regex mit dem Namen Einfanggruppen bekommen alle Spiele in Ruby

s="123--abc,123--abc,123--abc" 

ich versucht mit Ruby 1.9 die neue Funktion „genannten Gruppen“ alle Informationen benannte Gruppe zu holen:

/(?<number>\d*)--(?<chars>\s*)/ 

Gibt es eine API wie Pythons findall, die eine matchdata Sammlung zurückgibt? In diesem Fall muss ich zwei Übereinstimmungen zurückgeben, weil 123 und abc zweimal wiederholen. Jede Match-Datei enthält Details zu den einzelnen Capture-Informationen, so dass ich m['number'] verwenden kann, um den Match-Wert zu erhalten.

Antwort

29

Benannte Captures sind nur für ein übereinstimmendes Ergebnis geeignet.
Rubys Analogon von findall ist String#scan. Sie können entweder scan Ergebnis als Array verwenden, oder einen Block, um es passieren:

irb> s = "123--abc,123--abc,123--abc" 
=> "123--abc,123--abc,123--abc" 

irb> s.scan(/(\d*)--([a-z]*)/) 
=> [["123", "abc"], ["123", "abc"], ["123", "abc"]] 

irb> s.scan(/(\d*)--([a-z]*)/) do |number, chars| 
irb*  p [number,chars] 
irb> end 
["123", "abc"] 
["123", "abc"] 
["123", "abc"] 
=> "123--abc,123--abc,123--abc" 
+0

/(\ d *) - ([az] *)/Wenn ich diese Regex verwende, wie könnte ich die vollständige Match-Zeichenfolge erhalten, in diesem Fall ist ['123 - abc', '123 - abc '], dann kann ich Matchdaten für jedes Element selbst erstellen – mlzboy

+0

@mlzboy, gibt es zwei Lösungen. Am einfachsten ist es, die dritte Gruppe zu regex hinzuzufügen: '/ ((\ d *) - ([az] *))/do | all, Zahl, Zeichen |' – Nakilon

+2

danke, es scheint, dass der Ruby nicht unterstützt die genannte Capture-Funktion gut – mlzboy

2

@Nakilon korrekt ist scan mit einem regex zeigt, wie Sie brauchen nicht einmal in regex Land zu wagen, wenn Sie don‘ t wollen:

s = "123--abc,123--abc,123--abc" 
s.split(',') 
#=> ["123--abc", "123--abc", "123--abc"] 

s.split(',').inject([]) { |a,s| a << s.split('--'); a } 
#=> [["123", "abc"], ["123", "abc"], ["123", "abc"]] 

Dieses ein Array von Arrays zurückgibt, was sehr praktisch ist, wenn Sie mehrere Instanzen haben und müssen sehen,/sie alle verarbeiten.

Dies gibt einen Hash zurück, der, weil die Elemente den gleichen Schlüssel haben, nur den eindeutigen Schlüsselwert hat. Das ist gut, wenn Sie eine Reihe von doppelten Schlüsseln haben, aber die einzigartigen wollen. Der Nachteil tritt auf, wenn Sie die eindeutigen Werte benötigen, die den Schlüsseln zugeordnet sind, aber das scheint eine andere Frage zu sein.

+0

'Hash [s.split (", "). Map {| i | i.split ("-")}] ' – Nakilon

2

Vor einem Jahr wollte ich reguläre Ausdrücke, die die Captures zu lesen und Namen leichter waren, so dass ich die folgende Ergänzung String (sollte vielleicht nicht da sein, aber es war zu der Zeit praktisch):

scan2.rb:

class String 
    #Works as scan but stores the result in a hash indexed by variable/constant names (regexp PLACEHOLDERS) within parantheses. 
    #Example: Given the (constant) strings BTF, RCVR and SNDR and the regexp /#BTF# (#RCVR#) (#SNDR#)/ 
    #the matches will be returned in a hash like: match[:RCVR] = <the match> and match[:SNDR] = <the match> 
    #Note: The #STRING_VARIABLE_OR_CONST# syntax has to be used. All occurences of #STRING# will work as #{STRING} 
    #but is needed for the method to see the names to be used as indices. 
    def scan2(regexp2_str, mark='#') 
    regexp    = regexp2_str.to_re(mark)      #Evaluates the strings. Note: Must be reachable from here! 
    hash_indices_array = regexp2_str.scan(/\(#{mark}(.*?)#{mark}\)/).flatten #Look for string variable names within (#VAR#) or # replaced by <mark> 
    match_array   = self.scan(regexp) 

    #Save matches in hash indexed by string variable names: 
    match_hash = Hash.new 
    match_array.flatten.each_with_index do |m, i| 
     match_hash[hash_indices_array[i].to_sym] = m 
    end 
    return match_hash 
    end 

    def to_re(mark='#') 
    re = /#{mark}(.*?)#{mark}/ 
    return Regexp.new(self.gsub(re){eval $1}, Regexp::MULTILINE) #Evaluates the strings, creates RE. Note: Variables must be reachable from here! 
    end 

end 

Beispiel der Verwendung (irb1.9):

> load 'scan2.rb' 
> AREA = '\d+' 
> PHONE = '\d+' 
> NAME = '\w+' 
> "1234-567890 Glenn".scan2('(#AREA#)-(#PHONE#) (#NAME#)') 
=> {:AREA=>"1234", :PHONE=>"567890", :NAME=>"Glenn"} 

Anmerkungen:

Natürlich wäre es eleganter gewesen, die Muster (z.B. AREA, PHONE ...) in einem Hash und fügen diesen Hash mit Mustern zu den Argumenten von scan2 hinzu.

2

Bei der Verwendung von Rubin> = 1,9 und die genannten Aufnahmen, man kann:

class String 
    def scan2(regexp2_str, placeholders = {}) 
    return regexp2_str.to_re(placeholders).match(self) 
    end 

    def to_re(placeholders = {}) 
    re2 = self.dup 
    separator = placeholders.delete(:SEPARATOR) || '' #Returns and removes separator if :SEPARATOR is set. 
    #Search for the pattern placeholders and replace them with the regex 
    placeholders.each do |placeholder, regex| 
     re2.sub!(separator + placeholder.to_s + separator, "(?<#{placeholder}>#{regex})") 
    end  
    return Regexp.new(re2, Regexp::MULTILINE) #Returns regex using named captures. 
    end 
end 

Usage (Rubin> = 1.9):

> "1234:Kalle".scan2("num4:name", num4:'\d{4}', name:'\w+') 
=> #<MatchData "1234:Kalle" num4:"1234" name:"Kalle"> 

oder

> re="num4:name".to_re(num4:'\d{4}', name:'\w+') 
=> /(?<num4>\d{4}):(?<name>\w+)/m 

> m=re.match("1234:Kalle") 
=> #<MatchData "1234:Kalle" num4:"1234" name:"Kalle"> 
> m[:num4] 
=> "1234" 
> m[:name] 
=> "Kalle" 

Verwenden der Separator-Option:

> "1234:Kalle".scan2("#num4#:#name#", SEPARATOR:'#', num4:'\d{4}', name:'\w+') 
=> #<MatchData "1234:Kalle" num4:"1234" name:"Kalle"> 
7

Sie können die verwendeten Variablen aus dem regulären Ausdruck unter Verwendung names Verfahren extrahieren. Also, was ich getan habe ist, habe ich normale scan Methode, um die Übereinstimmungen zu erhalten, dann gezippte Namen und jedes Spiel, um eine Hash zu erstellen.

class String 
    def scan2(regexp) 
    names = regexp.names 
    scan(regexp).collect do |match| 
     Hash[names.zip(match)] 
    end 
    end 
end 

Verbrauch:

>> "aaa http://www.google.com.tr aaa https://www.yahoo.com.tr ddd".scan2 /(?<url>(?<protocol>https?):\/\/[\S]+)/ 
=> [{"url"=>"http://www.google.com.tr", "protocol"=>"http"}, {"url"=>"https://www.yahoo.com.tr", "protocol"=>"https"}] 
0

ich wirklich mochte @ Umut-Utkan-Lösung, aber es hat nicht ganz das tun, was ich wollte, so schrieb ich es ein wenig (beachten Sie, könnte die unten nicht schön sein Code, aber es scheint)

class String 
    def scan2(regexp) 
    names = regexp.names 
    captures = Hash.new 
    scan(regexp).collect do |match| 
     nzip = names.zip(match) 
     nzip.each do |m| 
     captgrp = m[0].to_sym 
     captures.add(captgrp, m[1]) 
     end 
    end 
    return captures 
    end 
end 

Nun zu arbeiten, wenn Sie das tun

p '12f3g4g5h5h6j7j7j'.scan2(/(?<alpha>[a-zA-Z])(?<digit>[0-9])/) 

Sie erhalten

{:alpha=>["f", "g", "g", "h", "h", "j", "j"], :digit=>["3", "4", "5", "5", "6", "7", "7"]} 

(dh. alle alphabetischen Zeichen in einem Array und alle Ziffern in einem anderen Array). Abhängig von Ihrem Zweck für das Scannen könnte dies nützlich sein. Wie auch immer, ich liebe es, Beispiele zu sehen, wie einfach es ist, die Kern-Ruby-Funktionalität mit nur ein paar Zeilen umzuschreiben oder zu erweitern!

2

Ich brauchte vor kurzem etwas Ähnliches. Dies sollte wie String#scan funktionieren, aber stattdessen ein Array von MatchData-Objekten zurückgeben.

class String 
    # This method will return an array of MatchData's rather than the 
    # array of strings returned by the vanilla `scan`. 
    def match_all(regex) 
    match_str = self 
    match_datas = [] 
    while match_str.length > 0 do 
     md = match_str.match(regex) 
     break unless md 
     match_datas << md 
     match_str = md.post_match 
    end 
    return match_datas 
    end 
end 

Lauf Ihre Beispieldaten in den REPL Ergebnisse in der folgenden:

> "123--abc,123--abc,123--abc".match_all(/(?<number>\d*)--(?<chars>[a-z]*)/) 
=> [#<MatchData "123--abc" number:"123" chars:"abc">, 
    #<MatchData "123--abc" number:"123" chars:"abc">, 
    #<MatchData "123--abc" number:"123" chars:"abc">] 

Sie können auch meine Test-Code nützlich finden:

describe String do 
    describe :match_all do 
    it "it works like scan, but uses MatchData objects instead of arrays and strings" do 
     mds = "ABC-123, DEF-456, GHI-098".match_all(/(?<word>[A-Z]+)-(?<number>[0-9]+)/) 
     mds[0][:word].should == "ABC" 
     mds[0][:number].should == "123" 
     mds[1][:word].should == "DEF" 
     mds[1][:number].should == "456" 
     mds[2][:word].should == "GHI" 
     mds[2][:number].should == "098" 
    end 
    end 
end 
+0

Persönlich würde ich dies zu der Regexp-Klasse gehören, aber das ist immer noch eine sehr nette Lösung. Überrascht, das ist keine Kernmethode. –

17

Chiming in Super-spät, aber hier ist eine einfache Möglichkeit, den String # -Scan zu replizieren, aber stattdessen die Matchdaten zu erhalten:

matches = [] 
foo.scan(regex){ matches << $~ } 

matches enthält jetzt die MatchData-Objekte, die dem Scannen der Zeichenfolge entsprechen.

+4

'$ LAST_MATCH_INFO' ist die analoge" englische "Variable für' $ ~ ', hoffe, dass es etwas googelt für Leute, die mehr lesbaren Code wollen. – Michael

+1

Es kommt alles auf Präferenz (oder Style Guides!), Aber einige würden sagen, $ $ 'ist lesbarer als' $ LAST_MATCH_DATA'. '$ ~' hat zwei Dinge: Es ist prägnanter (2 Zeichen gegenüber 12) und seine Tilde folgt dem Muster des regulären Ausdrucks, der den Operator '= ~' abgleicht. Natürlich sollten Sie immer so programmieren, was Ihr Publikum (Mitarbeiter, Future-You) leicht verstehen wird. Ich kann mich nie erinnern, was viele dieser $ Variablen tun: '$;', '$ &' und '$ @'? Wer kann sagen, ohne sie nachzuschauen? :-) –

+0

Wie funktioniert das? Was sichert '$ ~' ändert sich für jede Iteration? –

0

Ich mag die match_all von John gegeben, aber ich denke, es hat einen Fehler.

Die Linie:

match_datas << md 

funktioniert, wenn es keine Aufnahmen() in der Regex.

Dieser Code gibt die gesamte Zeile bis einschließlich des Musters an, das von der Regex abgeglichen/erfasst wurde. (Der [0] Teil von MatchData) Wenn die Regex capture() hat, dann ist dieses Ergebnis wahrscheinlich nicht das, was der Benutzer (ich) in der letztendlichen Ausgabe haben möchte.

ich im Fall denken, wo es erfaßt() in regex, der sollte korrekte Code sein:

match_datas << md[1] 

Die endgültige Ausgabe von match_datas wird eine Reihe von Mustern capture Spielen von match_datas beginnend [0] . Dies ist nicht ganz das, was erwartet werden kann, wenn ein normales MatchData gewünscht wird, das einen match_datas [0] -Wert enthält, der die ganze übereinstimmende Teilzeichenfolge gefolgt von match_datas [1], match_datas [[2], .. ist) im Regex-Muster.

Die Dinge sind komplex - weshalb match_all in nativen MatchData nicht enthalten sein könnte.

0

Huckepack off von Mark Hubbart Antwort, habe ich den folgenden Affen-Patch:

class ::Regexp 
    def match_all(str) 
    matches = [] 
    str.scan(self) { matches << $~ } 

    matches 
    end 
end 

die als /(?<letter>\w)/.match_all('word') verwendet werden kann, und kehrt:

[#<MatchData "w" letter:"w">, #<MatchData "o" letter:"o">, #<MatchData "r" letter:"r">, #<MatchData "d" letter:"d">]

Dies stützt sich auf, wie Andere haben gesagt, die Verwendung von $~ im Scan-Block für die Match-Daten.

Verwandte Themen