Muito bom!
Mas vale lembrar que, dependendo do caso, usar um quantificador lazy não garante que vc está livre do problema. Segue um exemplo retirado deste artigo:
Suponha que tem um arquivo CSV, e quero fazer uma regex que pegue o décimo segundo campo, desde que o primeiro caractere seja "P". Pra isso, uso a regex ^(.*?,){11}P
, que a princípio parece ok. O .*?,
pega zero ou mais caracteres seguido de vírgula, mas de maneira não-gananciosa para que o ponto não pegue uma vírgula. O quantificador {11}
garante que faço isso 11 vezes, e que o P
esteja no décimo segundo campo.
O problema acontece quando o décimo segundo campo não começa com "P". Supondo que a linha seja 1,2,3,4,5,6,7,8,9,10,11,12,13
: depois que o trecho ^(.*?,){11}
consumiu os 11 primeiros campos, a regex falha ao encontrar o "P" (pois o décimo segundo campo começa com o caractere 1
). Então ela faz o backtracking e o .*?
consome a vírgula depois do 11
.
Sim, afinal o ponto corresponde a qualquer caractere, inclusive a vírgula. O quantificador lazy não garante que o ponto nunca vai pegar uma vírgula, ele só garante que não vai pegar mais que o necessário. E neste caso, pegar zero vírgulas não foi o suficiente, então ele tenta de novo pegando uma.
Então agora a décima primeira ocorrência de (.*?,)
vai consumir todo o trecho 11,12,
e verificar se depois tem um "P", mas como não tem, ela faz mais um backtracking. E agora a décima primeira ocorrência passa a ser o trecho 11,12,13
, mas na verdade ela nem chega a ser completada, pois não tem uma vírgula no final. Lembre-se que 11,12,13
corresponde agora a .*?
, e depois teria que ter uma vírgula, mas como chegou ao final da string, a regex não encontra um match.
Só que ela não desiste! Agora ela faz o backtracking para a décima ocorrência de (.*?,)
, fazendo com que ela seja o trecho 10,11,
, e aí a décima primeira ocorrência é 12,
, para ver se depois tem um P
. E como não tem, faz mais um backtracking: a décima ocorrência passa a ser 10,11,12,
e assim vai até chegar ao final da string e não encontrar nenhum match.
E ela para pro aí? Claro que não, tem mais um backtracking: agora para a nona ocorrência, que será 9,10,
, depois 9,10,11,
e depois 9,10,11,12,
. Mas entre cada uma dessas tentativas, há mais possibilidades a serem testadas. Por exemplo, ao tentar 9,10,
, a décima ocorrência pode ser 11,
e 11,12,
. E mais uma vez ela chegará ao final da string sem encontrar um match e fará o backtracking para a oitava ocorrência (que abrirá mais possibilidades para a nona, décima, etc), depois novo backtracking para a sétima, e assim por diante.
Fiz um teste no Regex101, e neste link vc pode ver que todo esse backtracking gerou 32240 passos!
A solução, neste caso, foi mudar a expressão. O problema desta regex é que o ponto também pode incluir a própria vírgula, então o melhor é trocá-lo por uma expressão que não pegue a vírgula. Por exemplo, ^([^,\r\n]*,){11}P
. Troquei o ponto por [^,\r\n]
, que é "qualquer caractere que não seja vírgula ou quebra de linha". Repare que assim nem preciso usar o quantificador lazy, pois agora não existe o risco dele pegar uma vírgula por engano.
Aqui podemos ver que agora a regex só precisa de 65 passos para detectar que não existe um match.
Aproveitando, existe também um mito de que o quantificador lazy sempre é "melhor" (ou "pior", já ouvi as duas coisas), ou "mais rápido" (ou "mais lento", também já ouvi ambos), mas isso também não é verdade. Dependendo da regex e das strings sendo buscadas, ele pode ou não ser a melhor opção, conforme eu demonstro aqui.
O mesmo vale para as demais dicas, cada um pode ter casos específicos em que eles podem ser a causa do backtracking excessivo. Usar um ou outro nem sempre garante que vc está livre do problema, cada caso é um caso.
Perfeito exemplo, muito obrigado por compartilhar!!