JavaRush /Blogue Java /Random-PT /RegEx: 20 passos curtos para dominar expressões regulares...
Artur
Nível 40
Tallinn

RegEx: 20 passos curtos para dominar expressões regulares. Parte 3

Publicado no grupo Random-PT
RegEx: 20 passos curtos para dominar expressões regulares. Parte 1. RegEx: 20 passos curtos para dominar expressões regulares. Parte 2: Nesta parte passaremos para coisas um pouco mais complexas. Mas dominá-los, como antes, não será difícil. Repito que o RegEx é na verdade mais fácil do que pode parecer à primeira vista, e você não precisa ser um cientista espacial para dominá-lo e começar a usá-lo na prática. O original em inglês deste artigo está aqui . 20 passos curtos para dominar expressões regulares.  Parte 3 - 1

Etapa 11: parênteses ()como grupos de captura

20 passos curtos para dominar expressões regulares.  Parte 3 - 2No último problema, procuramos diferentes tipos de valores inteiros e valores numéricos de ponto flutuante (ponto). Mas o mecanismo de expressões regulares não diferenciava esses dois tipos de valores, pois tudo era capturado em uma grande expressão regular. Podemos dizer ao mecanismo de expressões regulares para diferenciar entre diferentes tipos de correspondências se colocarmos nossos minipadrões entre parênteses:
padrão: ([AZ])|([az]) 
string:   O atual presidente da Bolívia é Evo Morales .
correspondências: ^^^ ^^^^^^^ ^^^^^^^^^ ^^ ^^^^^^^ ^^ ^^^ ^^^^^^^ 
grupo:    122 2222222 122222222 22 1222222 22 122 1222222  
( Exemplo ) A expressão regular acima define dois grupos de captura que são indexados começando em 1. O primeiro grupo de captura corresponde a qualquer letra maiúscula e o segundo grupo de captura corresponde a qualquer letra minúscula. Usando o sinal 'ou' |e parênteses ()como um grupo de captura, podemos definir uma única expressão regular que corresponda a vários tipos de strings. Se aplicarmos isso ao nosso regex de pesquisa longo/flutuante da parte anterior do artigo, o mecanismo de regex capturará as correspondências correspondentes nos grupos apropriados. Ao verificar a qual grupo uma substring corresponde, podemos determinar imediatamente se é um valor flutuante ou um valor longo:
padrão: (\d*\.\d+[fF]|\d+\.\d*[fF]|\d+[fF])|(\d+[lL]) 
string:   42L 12 x 3,4f 6l 3,3 0F LF .2F 0.
correspondências: ^^^ ^^^^ ^^ ^^ ^^^ 
grupo:    222 1111 22 11 111  
( Exemplo ) Esta expressão regular é bastante complexa e, para entendê-la melhor, vamos decompô-la e examinar cada um destes padrões:
( // corresponde a qualquer substring "float"
  \d*\.\d+[fF]
  |
  \d+\.\d*[fF]
  |
  \d+[fF]
)
| // OU
( // corresponde a qualquer substring "longa"
  \d+[lL]
)
O sinal |e os grupos de captura entre parênteses ()nos permitem combinar diferentes tipos de substrings. Neste caso, estamos combinando números de ponto flutuante "float" ou números inteiros longos "long".
(
  \d*\.\d+[fF] // 1+ dígitos à direita da vírgula decimal
  |
  \d+\.\d*[fF] // 1+ dígitos à esquerda da vírgula decimal
  |
  \d+[fF] // sem ponto, apenas 1+ dígitos
)
|
(
  \d+[lL] // sem ponto, apenas 1+ dígitos
)
No grupo de captura "float", temos três opções: números com pelo menos 1 dígito à direita da vírgula, números com pelo menos 1 dígito à esquerda da vírgula e números sem vírgula. Qualquer um deles é "flutuante", desde que tenha as letras "f" ou "F" anexadas ao final. Dentro do grupo de captura “longo” só temos uma opção – devemos ter 1 ou mais dígitos seguidos do caractere “l” ou “L”. O mecanismo de expressão regular procurará essas substrings em uma determinada string e as indexará no grupo de captura apropriado. observaçãoque não estamos combinando nenhum dos números que não tenham "l", "L", "f" ou "F" adicionado a eles. Como esses números devem ser classificados? Bem, se eles tiverem um ponto decimal, o padrão da linguagem Java é "double". Caso contrário, eles deverão ser "int".

Vamos consolidar o que aprendemos com alguns quebra-cabeças:

Adicione mais dois grupos de captura ao regex acima para que ele também classifique números duplos ou int. (Esta é outra questão complicada, não desanime se demorar um pouco, como último recurso veja minha solução.)
padrão:
string:   42L 12 x 3,4f 6l 3,3 0F LF .2F 0. 
correspondências: ^^^ ^^ ^^^^ ^^ ^^^ ^^ ^^^ ^^ 
grupo:    333 44 1111 33 222 11 111 22
( Solução ) O próximo problema é um pouco mais simples. Use grupos de captura entre colchetes (), o sinal 'ou' |e intervalos de caracteres para classificar as seguintes idades: "é legal beber nos EUA". (>= 21) e “não é permitido beber nos EUA” (<21):
padrão:
string:   7 10 17 18 19 20 21 22 23 24 30 40 100 120 
correspondências: ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^ ^^^ 
grupo:    2 22 22 22 22 22 11 11 11 11 11 11 111 111 
( Solução )

Etapa 12: Identifique primeiro correspondências mais específicas

20 passos curtos para dominar expressões regulares.  Parte 3 - 3Você pode ter tido alguns problemas com a última tarefa se tentou definir “bebedores legais” como o primeiro grupo de captura em vez do segundo. Para entender o porquê, vejamos outro exemplo. Suponha que queiramos registrar separadamente sobrenomes contendo menos de 4 caracteres e sobrenomes contendo 4 ou mais caracteres. Vamos dar nomes mais curtos ao primeiro grupo de captura e ver o que acontece:
padrão: ([AZ][az]?[az]?)|([AZ][az][az][az]+) 
string:   Kim Job s Xu Clo yd Moh r Ngo Roc k.
correspondências: ^^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^^ 
grupo:    111 111 11 111 111 111 111   
( Exemplo ) Por padrão, a maioria dos mecanismos de expressões regulares usam correspondência gananciosa com os caracteres básicos que vimos até agora. Isso significa que o mecanismo de expressão regular capturará o grupo mais longo definido o mais cedo possível na expressão regular fornecida. Portanto, embora o segundo grupo acima possa capturar mais caracteres em nomes como "Jobs" e "Cloyd", por exemplo, mas como os três primeiros caracteres desses nomes já foram capturados pelo primeiro grupo de captura, eles não podem ser capturados novamente pelo segundo . Agora vamos fazer uma pequena correção - basta alterar a ordem dos grupos de captura, colocando primeiro o grupo mais específico (mais longo):
padrão: ([AZ][az][az][az]+)|([AZ][az]?[az]?) 
string:   Kim Jobs Xu Cloyd Mohr Ngo Rock .
correspondências: ^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^ 
grupo:    222 1111 22 11111 1111 222 1111    
( Exemplo )

Tarefa... desta vez apenas uma :)

Um padrão “mais específico” quase sempre significa “mais longo”. Digamos que queremos encontrar dois tipos de “palavras”: primeiro aquelas que começam com vogais (mais especificamente), depois aquelas que não começam com vogais (qualquer outra palavra). Tente escrever uma expressão regular para capturar e identificar strings que correspondam a esses dois grupos. (Os grupos abaixo são indicados por letras e não numerados. Você deve determinar qual grupo deve corresponder ao primeiro e qual deve corresponder ao segundo.)
padrão:
string:   pds6f uub 24r2gp ewqrty l ui_op 
corresponde: ^^^^^ ^^^ ^^^^^^ ^^^^^^ ^ ^^^^^ 
grupo:    NNNNN VVV NNNNNN VVVVVV N VVVVV
( Solução ) Em geral, quanto mais precisa for sua expressão regular, mais longa ela será. E quanto mais preciso for, menor será a probabilidade de você capturar algo que não precisa. Portanto, embora possam parecer assustadores, regexes mais longas ~= melhores regexes. Infelizmente .

Etapa 13: chaves {}para um número específico de repetições

20 passos curtos para dominar expressões regulares.  Parte 3 - 4No exemplo com sobrenomes da etapa anterior, tivemos 2 grupos quase repetidos em um padrão:
padrão: ([AZ][az][az][az]+)|([AZ][az]?[az]?) 
string:   Kim Jobs Xu Cloyd Mohr Ngo Rock .
correspondências: ^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^ 
grupo:    222 1111 22 11111 1111 222 1111    
Para o primeiro grupo, precisávamos de sobrenomes com quatro ou mais letras. O segundo grupo teve que capturar sobrenomes com três ou menos letras. Existe alguma maneira mais fácil de escrever isso do que repetir esses [a-z]grupos indefinidamente? Existe se você usar chaves para isso {}. As chaves {}nos permitem especificar o número mínimo e (opcionalmente) máximo de correspondências do caractere ou grupo de captura anterior. Existem três casos de uso {}:
{X} // corresponde exatamente X vezes
{X,} // corresponde >= X vezes
{X,Y} // corresponde >= X e <= Y vezes
Aqui estão exemplos dessas três sintaxes diferentes:
padrão: [az]{11} 
string:   humuhumunuk unukuapua'a.
correspondências: ^^^^^^^^^^^   
( Exemplo )
padrão: [az]{18,} 
string:   humuhumunukunukuapua 'a.
correspondências: ^^^^^^^^^^^^^^^^^^^^^    
( Exemplo )
padrão: [az]{11,18} 
string:   humuhumunukunukuap ua'a.
correspondências: ^^^^^^^^^^^^^^^^^^    
( Exemplo ) Há vários pontos a serem observados nos exemplos acima.observação:. Primeiro, usando a notação {X}, o caractere ou grupo anterior corresponderá exatamente a esse número (X) de vezes. Se houver mais caracteres na "palavra" (do que o número X) que possam corresponder ao padrão (como mostrado no primeiro exemplo), eles não serão incluídos na correspondência. Se o número de caracteres for menor que X, a correspondência completa falhará (tente alterar 11 para 99 no primeiro exemplo). Em segundo lugar, as notações {X,} e {X,Y} são gananciosas. Eles tentarão combinar o maior número possível de caracteres e, ao mesmo tempo, satisfazer a expressão regular fornecida. Se você especificar {3,7}, 3 a 7 caracteres poderão ser correspondidos e se os próximos 7 caracteres forem válidos, todos os 7 caracteres serão correspondidos. Se você especificar {1,} e todos os próximos 14.000 caracteres corresponderem, todos os 14.000 caracteres serão incluídos na sequência correspondente. Como podemos usar esse conhecimento para reescrever nossa expressão acima? A melhoria mais simples poderia ser substituir os grupos vizinhos [a-z]por [a-z]{N}, onde N é escolhido de acordo:
padrão: ([AZ][az]{2}[az]+)|([AZ][az]?[az]?)  
...mas isso não torna as coisas muito melhores. Veja o primeiro grupo de captura: temos [a-z]{2}(que corresponde exatamente a 2 letras minúsculas) seguido de [a-z]+(que corresponde a 1 ou mais letras minúsculas). Podemos simplificar isso pedindo 3 ou mais letras minúsculas usando chaves:
padrão: ([AZ][az]{3,})|([AZ][az]?[az]?) 
O segundo grupo de captura é diferente. Não precisamos de mais do que três caracteres nesses sobrenomes, o que significa que temos um limite superior, mas nosso limite inferior é zero:
padrão: ([AZ][az]{3,})|([AZ][az]{0,2}) 
A especificidade é sempre melhor ao usar expressões regulares, então seria sensato parar por aí, mas não posso deixar de notar que esses dois intervalos de caracteres ( [AZ]e [az]) próximos um do outro parecem quase uma classe de "caractere de palavra", \w( [A-Za-z0-9_]) . Se tivermos certeza de que nossos dados contêm apenas sobrenomes bem formatados, poderíamos simplificar nossa expressão regular para simplesmente escrever:
padrão: (\w{4,})|(\w{1,3}) 
O primeiro grupo captura qualquer sequência de 4 ou mais “caracteres de palavras” ( [A-Za-z0-9_]), e o segundo grupo captura qualquer sequência de 1 a 3 “caracteres de palavras” (inclusive). Isso vai funcionar?
padrão: (\w{4,})|(\w{1,3}) 
string:   Kim Jobs Xu Cloyd Mohr Ngo Rock .
correspondências: ^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^ 
grupo:    222 1111 22 11111 1111 222 1111    
( Exemplo ) Funcionou! Que tal essa abordagem? E é muito mais limpo que o nosso exemplo anterior. Como o primeiro grupo de captura corresponde a todos os sobrenomes com quatro ou mais caracteres, poderíamos até alterar o segundo grupo de captura para simplesmente \w+, pois isso nos permitiria capturar todos os sobrenomes restantes (com 1, 2 ou 3 caracteres):
padrão: (\w{4,})|(w+) 
string:   Kim Jobs Xu Cloyd Mohr Ngo Rock .
correspondências: ^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^ 
grupo:    222 1111 22 11111 1111 222 1111    
( Exemplo )

Vamos ajudar o cérebro a aprender isso e resolver os 2 problemas a seguir:

Use chaves {}para reescrever a expressão regular de pesquisa do número do seguro social da etapa 7:
padrão:
sequência: 113-25=1902 182-82-0192 H23-_3-9982 1I1-O0-E38B
correspondências:              ^^^^^^^^^^^
( Solução ) Suponha que o verificador de força da senha de um site exija que as senhas dos usuários tenham entre 6 e 12 caracteres. Escreva uma expressão regular que sinalize as senhas inválidas na lista abaixo. Cada senha está entre parênteses ()para facilitar a correspondência, portanto, certifique-se de que a expressão regular comece e termine com caracteres literais (e simbólicos. )Dica: certifique-se de proibir parênteses literais em senhas com [^()]ou similares, caso contrário você acabará combinando a string inteira!
padrão:
string:   (12345) (minha senha) (Xanadu.2112) (su_do) (OfSalesmen!)
correspondências: ^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^  
( Solução )

Etapa 14: \bSímbolo de borda com largura zero

20 passos curtos para dominar expressões regulares.  Parte 3 - 5A última tarefa foi bastante difícil. Mas e se tornássemos tudo um pouco mais complicado, colocando as senhas entre aspas ""em vez de parênteses ()? Podemos escrever uma solução semelhante simplesmente substituindo todos os caracteres entre parênteses por aspas?
padrão: \"[^"]{0.5}\"|\"[^"]+\s[^"]*\" 
string:   "12345" "minha senha" "Xanadu.2112 " " su_do" " OfSalesmen! "
correspondências: ^^^^^^^ ^^^^^^^^^^^^^ ^^^ ^^^  
( Exemplo ) Não ficou muito impressionante. Você já adivinhou por quê? O problema é que estamos procurando senhas incorretas aqui. "Xanadu.2112" é uma boa senha, então quando a regex percebe que essa sequência não contém espaços ou caracteres literais ", ela rende logo antes do caractere "que qualifica a senha do lado direito. (Porque especificamos que os caracteres "não podem ser encontrados dentro de senhas usando [^"].) Quando o mecanismo de expressões regulares estiver satisfeito com o fato de que esses caracteres não correspondem a uma expressão regular específica, ele será executado novamente, exatamente de onde parou - onde o caractere estava ". Xanadu.2112" à direita. A partir daí ele vê um caractere de espaço e outro caractere "- para ele esta é a senha errada! Basicamente, ele encontra essa sequência " "e segue em frente. Não é isso que gostaríamos de obter... Seria ótimo se pudéssemos especificar que o primeiro caracter da senha não deveria ser um espaço. Existe uma maneira de fazer isso? (A esta altura, você provavelmente já percebeu que a resposta a todas as minhas perguntas retóricas é “sim”.) Sim! Existe tal maneira! Muitos mecanismos de expressões regulares fornecem uma sequência de escape como "word border" \b. "Limite de palavra" \bé uma sequência de escape de largura zero que, curiosamente, corresponde a um limite de palavra. Lembre-se de que quando dizemos “palavra”, queremos dizer qualquer sequência de caracteres da classe \wou [A-Za-z0-9_]. Uma correspondência de limite de palavra significa que o caractere imediatamente antes ou imediatamente após a sequência \bdeve ser неum caractere de palavra. No entanto, ao fazer a correspondência, não incluímos esse caractere em nossa substring capturada. Esta é a largura zero. Para ver como isso funciona, vejamos um pequeno exemplo:
padrão: \b[^ ]+\b 
string:   Ainda precisamos de dinheiro , Lebowski .
correspondências: ^^ ^^^^^ ^^^^ ^^ ^^^^^ ^^^^^^^^^  
( Exemplo ) A sequência [^ ]deve corresponder a qualquer caractere que não seja um caractere de espaço literal. Então, por que isso não corresponde à vírgula ,depois do dinheiro ou ao ponto final " .depois de Lebowski? Isso ocorre porque a vírgula ,e o ponto final .não são caracteres de palavras, então os limites são criados entre caracteres de palavras e caracteres que não são de palavras. Eles aparecem entre yo final do palavra dinheiro e a vírgula ,que a segue. e entre " ia palavra Lebowski e o ponto final .(ponto final/ponto final) que a segue. A expressão regular corresponde aos limites dessas palavras (mas não aos caracteres que não são palavras, que apenas ajudam a defini-las). Mas o que acontece se não incluirmos consistência \bno nosso modelo?
padrão: [^] + 
string:   Ainda quero dinheiro, Lebowski. 
correspondências: ^^ ^^^^^ ^^^^ ^^ ^^^^^^ ^^^^^^^^^^  
( Exemplo ) Sim, agora também encontramos esses sinais de pontuação. Agora vamos usar limites de palavras para corrigir o regex para senhas entre aspas:
padrão: \"\b[^"]{0.5}\b\"|\"\b[^"]+\s[^"]*\b\" 
string:   "12345" "minha senha" " Xanadu. 2112" "su_do" "Dos Vendedores!"
correspondências: ^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^  
( Exemplo ) Ao colocar os limites das palavras entre aspas ("\b ... \b"), estamos efetivamente dizendo que o primeiro e o último caracteres das senhas correspondentes devem ser "caracteres de palavras". Portanto, isso funciona bem aqui, mas não funcionará tão bem se o primeiro ou o último caractere da senha do usuário não for um caractere de palavra:
padrão: \"\b[^"]{0.5}\b\"|\"\b[^"]+\s[^"]*\b\"
string: "a seguinte senha é muito curta" "C++"
partidas:   
( Exemplo ) Veja como a segunda senha não está marcada como "inválida", embora seja claramente muito curta. Você deve sercuidadosocom sequências \b, pois elas apenas correspondem aos limites entre caracteres \we não \w. No exemplo acima, como permitimos caracteres not , em passwords \w, não há garantia de que o limite entre \o primeiro/último caractere da senha seja um limite de palavra \b.

Para completar esta etapa, resolveremos apenas um problema simples:

Os limites de palavras são úteis em mecanismos de realce de sintaxe quando queremos corresponder uma sequência específica de caracteres, mas queremos ter certeza de que eles ocorrem apenas no início ou no final de uma palavra (ou sozinhos). Digamos que estamos escrevendo realce de sintaxe e queremos destacar a palavra var, mas apenas quando ela aparece sozinha (sem tocar em outros caracteres da palavra). Você pode escrever uma expressão regular para isso? Claro que pode, é uma tarefa muito simples ;)
padrão:
string:   var varx _var ( var j) barvarcar * var var -> { var }
correspondências: ^^^ ^^^ ^^^ ^^^ ^^^  
( Solução )

Etapa 15: “caret” ^como “início da linha” e cifrão $como “fim da linha”

20 passos curtos para dominar expressões regulares.  Parte 3 - 6A sequência de limite de palavras \b(da última etapa da parte anterior do artigo) não é a única sequência especial de largura zero disponível para uso em expressões regulares. Os dois mais populares são "caret" ^- "início da linha" e cifrão $- "fim da linha". Incluir um destes em suas expressões regulares significa que a correspondência deve aparecer no início ou no final da string de origem:
padrão: ^início|fim$ 
string:   início fim início fim início fim início fim 
correspondências: ^^^^^ ^^^  
( Exemplo ) Se sua string contiver quebras de linha, ela ^startcorresponderá à sequência "início" no início de qualquer linha e end$à sequência "fim" no final de qualquer linha (embora isso seja difícil de mostrar aqui). Esses símbolos são especialmente úteis ao trabalhar com dados que contêm delimitadores. Vamos voltar à questão do “tamanho do arquivo” da etapa 9 usando ^“início da linha”. Neste exemplo, nossos tamanhos de arquivo são separados por espaços " ". Portanto, queremos que cada tamanho de arquivo comece com um número, precedido por um caractere de espaço ou pelo início de uma linha:
padrão: (^| )(\d+|\d+\.\d+)[KMGT]B 
string:   6,6KB 1..3KB 12KB 5G 3,3MB KB .6,2TB 9MB .
correspondências: ^^^^^ ^^^^^ ^^^^^^ ^^^^ 
grupo:    222 122 1222 12    
( Exemplo ) Já estamos tão perto da meta! Mas você pode notar que ainda temos um pequeno problema: estamos combinando o caractere de espaço antes do tamanho válido do arquivo. Agora podemos simplesmente ignorar este grupo de captura (1) quando nosso mecanismo de expressão regular o encontrar, ou podemos usar um grupo de não captura, que veremos na próxima etapa.

Enquanto isso, vamos resolver mais 2 problemas de tom:

Continuando com nosso exemplo de realce de sintaxe da última etapa, alguns realces de sintaxe marcarão espaços finais, ou seja, quaisquer espaços que estejam entre um caractere que não seja um espaço em branco e o final da linha. Você pode escrever um regex para destacar apenas os espaços finais?
padrão:
string: meuvec <- c(1, 2, 3, 4, 5)  
correspondências:                          ^^^^^^^  
( Solução ) Um analisador simples de valores separados por vírgula (CSV) procurará "tokens" separados por vírgulas. Geralmente, o espaço não tem significado a menos que esteja entre aspas "". Escreva uma expressão regular de análise CSV simples que corresponda aos tokens entre vírgulas, mas ignore (não capture) espaços em branco que não estejam entre aspas.
padrão:
string:   a, "b", "c d",e,f, "g h", dfgi,, k, "", l 
corresponde a: ^^ ^^^^ ^^^^^^^^^^ ^^^ ^^^ ^^^^^^ ^^ ^^^ ^ 
grupo:    21 2221 2222212121 222221 222211 21 221 2    
( Solução ) RegEx: 20 passos curtos para dominar expressões regulares. Parte 4.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION