Jak dopasować wszystkie wystąpienia wyrażenia regularnego

586

Czy istnieje szybki sposób na znalezienie każdego dopasowania wyrażenia regularnego w Ruby? Przejrzałem obiekt Regex w Ruby STL i szukałem w Google bezskutecznie.

Chris Bunch
źródło
3
Przeczytałem, że w ten sposób mogę wyszukać ciąg znaków dla wszystkich wzorców
wyrażeń

Odpowiedzi:

821

Korzystanie scanpowinno załatwić sprawę:

string.scan(/regex/)
Drelich
źródło
9
Ale co z tą sprawą? „dopasuj mnie!”. scan (/.../) = [„mat”, „ch” „me!” ], ale wszystkie wystąpienia /.../ byłyby [„mat”, „atc”, „tch”, „ch”, ...]
Michael Dickens,
13
Nie byłoby inaczej. /.../ jest normalnym, chciwym wyrażeniem regularnym. Nie cofnie się w przypadku dopasowanych treści. możesz spróbować użyć leniwego wyrażenia regularnego, ale nawet to prawdopodobnie nie wystarczy. spójrz na wyrażenie regularne doc ruby-doc.org/core-1.9.3/Regexp.html, aby poprawnie wyrazić swoje wyrażenie regularne :)
Jean
49
to wygląda jak Ruby WTF ... dlaczego to jest na String zamiast Regexp z innymi regexp? Nigdzie nie wspomniano o nim w dokumentach dotyczących
Regexp
9
Wydaje mi się, że dzieje się tak, ponieważ jest zdefiniowane i wywołane na String, a nie na Regex ... Ale to naprawdę ma sens. Możesz napisać wyrażenie regularne, aby przechwycić wszystkie dopasowania za pomocą Regex # match i iterować przechwycone grupy. Tutaj piszesz funkcję dopasowania częściowego i chcesz, aby była stosowana wiele razy na danym ciągu, nie jest to obowiązkiem Regexp. Sugeruję sprawdzenie implementacji skanu dla lepszego zrozumienia: ruby-doc.org/core-1.9.3/String.html#method-i-scan
Jean
9
@MichaelDickens: W tym przypadku możesz użyć /(?=(...))/.
Konrad Borowski
67

Aby znaleźć wszystkie pasujące ciągi, użyj scanmetody Ciąg .

str = "A 54mpl3 string w1th 7 numb3rs scatter36 ar0und"
str.scan(/\d+/)
#=> ["54", "3", "1", "7", "3", "36", "0"]

Jeśli chcesz, MatchDataczyli typu obiektu zwracanego przez matchmetodę Regexp , użyj:

str.to_enum(:scan, /\d+/).map { Regexp.last_match }
#=> [#<MatchData "54">, #<MatchData "3">, #<MatchData "1">, #<MatchData "7">, #<MatchData "3">, #<MatchData "36">, #<MatchData "0">]

Zaletą korzystania MatchDatajest to, że możesz używać metod takich jak offset:

match_datas = str.to_enum(:scan, /\d+/).map { Regexp.last_match }
match_datas[0].offset(0)
#=> [2, 4]
match_datas[1].offset(0)
#=> [7, 8]

Zobacz te pytania, jeśli chcesz dowiedzieć się więcej:

Czytając o zmiennych specjalnych $&, $', $1, $2Ruby będzie też pomocny.

Sudo Bangbang
źródło
12

jeśli masz wyrażenie regularne z grupami:

str="A 54mpl3 string w1th 7 numbers scatter3r ar0und"
re=/(\d+)[m-t]/

możesz użyć scanmetody String, aby znaleźć pasujące grupy:

str.scan re
#> [["54"], ["1"], ["3"]]

Aby znaleźć pasujący wzór:

str.to_enum(:scan,re).map {$&}
#> ["54m", "1t", "3r"]
MVP
źródło
str.scan(/\d+[m-t]/) # => ["54m", "1t", "3r"]jest bardziej idiomatyczny niżstr.to_enum(:scan,re).map {$&}
Tin Man
Może źle zrozumiałeś. Wyrażenie regularne w przykładzie użytkownika, na który odpowiedziałem brzmiało: /(\d+)[m-t]/nie /\d+[m-t]/pisać: re = /(\d+)[m-t]/; str.scan(re)jest to samo, str.scan(/(\d+)[mt]/)ale dostaję #> [["" 54 "], [" 1 "], [" 3 "]]i nie "54m", "1t", "3r"]Pytanie brzmiało: czy mam wyrażenie regularne z grupą i chcę uchwycić wszystkie wzorce bez zmiany regularnego wyrażenie (opuszczenie grupy), jak mogę to zrobić? W tym sensie możliwym rozwiązaniem, choć nieco tajemniczym i trudnym do odczytania, było:str.to_enum(:scan,re).map {$&}
MVP
-1

Możesz użyć string.scan(your_regex).flatten. Jeśli wyrażenie regularne zawiera grupy, zwróci się w jednej prostej tablicy.

string = "A 54mpl3 string w1th 7 numbers scatter3r ar0und"
your_regex = /(\d+)[m-t]/
string.scan(your_regex).flatten
=> ["54", "1", "3"]

Regex może być również nazwaną grupą.

string = 'group_photo.jpg'
regex = /\A(?<name>.*)\.(?<ext>.*)\z/
string.scan(regex).flatten

Możesz także użyć gsub, to tylko jeden sposób, jeśli chcesz MatchData.

str.gsub(/\d/).map{ Regexp.last_match }
Datt
źródło
Usuń zgrupowanie your_regex = /(\d+)[m-t]/i nie będziesz musiał używać flatten. Ostatni przykład użycia, last_matchktóry w tym przypadku jest prawdopodobnie bezpieczny, ale jest globalny i może zostać zastąpiony, jeśli jakieś wyrażenie regularne zostanie dopasowane przed wywołaniem last_match. Zamiast tego jest to prawdopodobnie bezpieczniejsze w użyciu string.match(regex).captures # => ["group_photo", "jpg"]lub string.scan(/\d+/) # => ["54", "3", "1", "7", "3", "0"]jak pokazano w innych odpowiedziach, w zależności od wzorca i potrzeb.
Tin Man