Como combinar todas as ocorrências de uma regex

586

Existe uma maneira rápida de encontrar todas as correspondências de uma expressão regular no Ruby? Eu olhei através do objeto Regex no Ruby STL e procurei no Google sem sucesso.

Chris Bunch
fonte
3
Eu li isso é como eu posso procurar uma string para todos os padrões de regex e foi terrivelmente confuso ...
Hugoagogo

Respostas:

821

Usar scandeve fazer o truque:

string.scan(/regex/)
Jean
fonte
9
Mas o que é esse caso? "combine comigo!". scan (/.../) = ["mat", "ch" "eu!" ], mas todas as ocorrências de /.../ seriam ["mat", "atc", "tch", "ch", ...]
Michael Dickens
13
Não, não seria. /.../ é um regexp ganancioso normal. Não retornará ao conteúdo correspondente. você pode tentar usar um regexp lento, mas mesmo isso provavelmente não será suficiente. ter um olhar para o doc regexp ruby-doc.org/core-1.9.3/Regexp.html para expressar correctamente o seu regexp :)
Jean
49
isso parece um WTF Ruby ... por que isso está em String em vez de Regexp com as outras coisas de regexp? Nem sequer é mencionado em qualquer lugar sobre os docs para Regexp
Anentropic
9
Eu acho que é porque é definido e chamado String, não no Regex ... Mas, na verdade, faz sentido. Você pode escrever uma expressão regular para capturar todas as correspondências usando Regex # match e iterar sobre os grupos capturados. Aqui, você escreve uma função de correspondência parcial e deseja que ela seja aplicada várias vezes em uma determinada sequência, isso não é responsabilidade do Regexp. Eu sugiro que você verificar a aplicação de digitalização para obter uma melhor compreensão: ruby-doc.org/core-1.9.3/String.html#method-i-scan
Jean
9
@ MichaelDickens: Nesse caso, você pode usar /(?=(...))/.
Konrad Borowski
67

Para encontrar todas as seqüências correspondentes, use o scanmétodo String .

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

Se você quiser, MatchDataque é o tipo do objeto retornado pelo matchmétodo Regexp , use:

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

A vantagem de usar MatchDataé que você pode usar métodos como 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]

Veja estas perguntas se quiser saber mais:

Lendo sobre variáveis especiais $&, $', $1, $2em Ruby será útil também.

sudo bangbang
fonte
12

se você tiver uma regexp com grupos:

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

você pode usar o scanmétodo String para encontrar grupos correspondentes:

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

Para encontrar o padrão correspondente:

str.to_enum(:scan,re).map {$&}
#> ["54m", "1t", "3r"]
MVP
fonte
str.scan(/\d+[m-t]/) # => ["54m", "1t", "3r"]é mais idiomático do questr.to_enum(:scan,re).map {$&}
o Homem de Lata
Talvez você tenha entendido errado. A expressão regular do exemplo de um usuário que eu respondi foi: /(\d+)[m-t]/não /\d+[m-t]/Escrever: re = /(\d+)[m-t]/; str.scan(re)é o mesmo, str.scan(/(\d+)[mt]/)mas recebo #> [["" 54 "], [" 1 "], [" 3 "]]e não "54m", "1t", "3r"]A pergunta era: se eu tenho uma expressão regular com um grupo e quero capturar todos os padrões sem alterar o padrão expressão (saindo do grupo), como posso fazer isso? Nesse sentido, uma solução possível, embora um pouco enigmática e difícil de ler, foi:str.to_enum(:scan,re).map {$&}
MVP
-1

Você pode usar string.scan(your_regex).flatten. Se o seu regex contiver grupos, ele retornará em uma única matriz simples.

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

Regex também pode ser um grupo nomeado.

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

Você também pode usar gsub, é apenas mais uma maneira se você quiser o MatchData.

str.gsub(/\d/).map{ Regexp.last_match }
Datt
fonte
Remova o agrupamento de your_regex = /(\d+)[m-t]/e você não precisará usá-lo flatten. Seu exemplo final usa last_matchque, nesse caso, provavelmente é seguro, mas é global e pode ser substituído se qualquer expressão regular corresponder antes da chamada last_match. Em vez disso, é provavelmente mais seguro de usar string.match(regex).captures # => ["group_photo", "jpg"]ou string.scan(/\d+/) # => ["54", "3", "1", "7", "3", "0"]como mostrado em outras respostas, dependendo do padrão e das necessidades.
the Tin Man