Как сопоставить все вхождения регулярного выражения


586

Есть ли быстрый способ найти каждое совпадение регулярного выражения в Ruby? Я просмотрел объект Regex в Ruby STL и искал в Google безрезультатно.


3
Я прочитал, как я могу найти строку для всех шаблонов регулярных выражений и был ужасно смущен ...
Hugoagogo

Ответы:


821

Использование scanдолжно сделать трюк:

string.scan(/regex/)

9
Но что упираться в этот случай? "соответствуй мне!". scan (/.../) = ["mat", "ch" "me!" ], но все вхождения /.../ были бы ["mat", "atc", "tch", "ch", ...]
Майкл Диккенс

13
Не было бы. /.../ - нормальное жадное регулярное выражение. Он не будет возвращаться к подобранному контенту. Вы можете попробовать использовать ленивое регулярное выражение, но даже этого, вероятно, будет недостаточно. взгляните на regexp doc ruby-doc.org/core-1.9.3/Regexp.html, чтобы правильно выразить свое регулярное выражение :)
Жан

49
это похоже на Ruby WTF ... почему это на String вместо Regexp с другими вещами regexp? Это даже нигде не упоминается в документах для Regexp
Anentropic

9
Я думаю, это потому, что он определен и вызван для String, а не для Regex ... Но это действительно имеет смысл. Вы можете написать регулярное выражение для захвата всех совпадений, используя Regex # match, и перебирать захваченные группы. Здесь вы пишете функцию частичного соответствия и хотите, чтобы она применялась несколько раз к заданной строке, это не является обязанностью Regexp. Я предлагаю вам проверить реализацию сканирования для лучшего понимания: ruby-doc.org/core-1.9.3/String.html#method-i-scan
Jean

9
@MichaelDickens: В этом случае вы можете использовать /(?=(...))/.
Конрад Боровски,

67

Чтобы найти все подходящие строки, используйте scanметод String .

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

Если хотите, то MatchDataесть тип объекта, возвращаемого методом Regexp match, используйте:

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

Преимущество использования MatchDataзаключается в том, что вы можете использовать такие методы, как 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]

Посмотрите эти вопросы, если вы хотите узнать больше:

Чтение о специальных переменных $&, $', $1, $2в Ruby , будет полезно тоже.


12

если у вас есть регулярное выражение с группами:

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

Вы можете использовать scanметод String для поиска подходящих групп:

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

Чтобы найти соответствующий шаблон:

str.to_enum(:scan,re).map {$&}
#> ["54m", "1t", "3r"]

str.scan(/\d+[m-t]/) # => ["54m", "1t", "3r"]более идиоматичен, чемstr.to_enum(:scan,re).map {$&}
Жестянщик

Может быть, вы неправильно поняли. Регулярное выражение примера пользователя, на который я ответил, было: /(\d+)[m-t]/не /\d+[m-t]/писать: то re = /(\d+)[m-t]/; str.scan(re)же самое, str.scan(/(\d+)[mt]/)но я получаю #>, [["" 54 "], [" 1 "], [" 3 "]]а не "54m", "1t", "3r"]вопрос: было ли у меня регулярное выражение с группой, и я хочу захватить все шаблоны, не меняя регулярные выражение (выход из группы), как я могу это сделать? В этом смысле возможное решение, хотя и немного загадочное и трудное для чтения, было:str.to_enum(:scan,re).map {$&}
MVP

-1

Вы можете использовать string.scan(your_regex).flatten. Если ваше регулярное выражение содержит группы, оно вернется в виде одного простого массива.

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

Regex также может быть именованной группой.

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

Вы также можете использовать gsub, это просто еще один способ, если вы хотите MatchData.

str.gsub(/\d/).map{ Regexp.last_match }

Удалите группу из, your_regex = /(\d+)[m-t]/и вам не нужно будет использовать flatten. Ваш последний пример использует, last_matchкоторый в этом случае, вероятно, является безопасным, но является глобальным и может быть перезаписан, если перед вызовом было найдено какое-либо регулярное выражение last_match. Вместо этого, вероятно, безопаснее использовать string.match(regex).captures # => ["group_photo", "jpg"]или, string.scan(/\d+/) # => ["54", "3", "1", "7", "3", "0"]как показано в других ответах, в зависимости от схемы и потребностей.
Жестянщик
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.