Regex exatamente n OR m vezes

105

Considere a seguinte expressão regular, onde Xé qualquer regex.

X{n}|X{m}

Esta regex seria testada para Xocorrer exatamente n ou mvezes.

Existe um quantificador regex que pode testar uma ocorrência Xexatamente nou mvezes?

FThompson
fonte
Não. Duas ocorrências de Xé o melhor que você pode obter para o geral m, n.
John Dvorak
Se esse fosse meu problema, eu tentaria backreferences regex e começaria com (X)\1{n-1}(?:\1{m-n-1}). Eu sei que isso corresponde Xpelo menos uma vez, mas apenas para começar, tente essa coisa simples e, em seguida, refine usando lookaheads ou lookbehinds em vez de (X).
basicamente

Respostas:

91

Não existe um quantificador único que significa "exatamente m ou n vezes". A maneira como você está fazendo isso é ótima.

Uma alternativa é:

X{m}(X{k})?

onde m < ne ké o valor de n-m.

Mark Byers
fonte
67

Aqui está a lista completa de quantificadores (ref. Http://www.regular-expressions.info/reference.html ):

  • ?, ??- 0 ou 1 ocorrências ( ??é preguiçoso, ?é ganancioso)
  • *, *?- qualquer número de ocorrências
  • +, +?- pelo menos uma ocorrência
  • {n}- exatamente nocorrências
  • {n,m}- npara mocorrências, inclusive
  • {n,m}?- npara mocorrências, preguiçoso
  • {n,}, {n,}?- pelo menos nocorrência

Para obter "exatamente N ou M", você precisa escrever o regex quantificado duas vezes, a menos que m, n sejam especiais:

  • X{n,m} E se m = n+1
  • (?:X{n}){1,2} E se m = 2n
  • ...
John Dvorak
fonte
1
Por que o é ?:necessário no m = 2nexemplo if ? Parece funcionar bem sem ele para mim.
erb
7
@erb se você o deixar de fora ?:, o grupo se torna um grupo de captura. Além do mecanismo de regex lembrar coisas que não precisa, se você tiver grupos de captura após esse, seus IDs serão alterados. Se você usar sua regex para substituição, terá que ajustar a substituição.
John Dvorak
3

TLDR; (?<=[^x]|^)(x{n}|x{m})(?:[^x]|$)

Parece que você quer "xn times" ou "xm times", acho que uma tradução literal para regex seria (x{n}|x{m}). assim https://regex101.com/r/vH7yL5/1

ou, em um caso onde você pode ter uma sequência de mais de m "x" s (assumindo m> n), você pode adicionar 'não seguindo "x"' e 'seguido por nenhum "x", traduzindo para [^x](x{n}|x{m})[^x]mas isso seria suponha que sempre há um caractere atrás e depois de "x" s. Como você pode ver aqui: https://regex101.com/r/bB2vH2/1

você pode alterá-lo para (?:[^x]|^)(x{n}|x{m})(?:[^x]|$), traduzindo para "não seguindo 'x' ou seguindo o início da linha" e "seguido por nenhum 'x' ou seguido pelo fim da linha". Mas, ainda assim, não combinará duas sequências com apenas um caractere entre elas (porque a primeira correspondência exigiria um caractere depois, e a segunda um caractere antes) como você pode ver aqui: https://regex101.com/r/ oC5oJ4 / 1

Finalmente, para combinar a correspondência distante de um caractere, você pode adicionar um olhar positivo para frente (? =) No "não 'x' depois" ou um olhar positivo para trás (? <=) No "não 'x' antes", assim: https://regex101.com/r/mC4uX3/1

(?<=[^x]|^)(x{n}|x{m})(?:[^x]|$)

Dessa forma, você corresponderá apenas ao número exato de 'x's que deseja.

Energizado
fonte
1

Observando a resposta de Enhardened, eles afirmam que sua penúltima expressão não corresponderá a sequências com apenas um caractere entre elas. Há uma maneira fácil de consertar isso sem usar look ahead / look behind, que é substituir o caractere de início / fim pelo caractere de limite. Isso permite que você compare os limites das palavras, incluindo início / fim. Como tal, a expressão apropriada deve ser:

(?:[^x]|\b)(x{n}|x{m})(?:[^x]|\b)

Como você pode ver aqui: https://regex101.com/r/oC5oJ4/2 .

rozza2058
fonte
1
Legal, eu não estava familiarizado com a forma como o regex lida com os limites. O único problema com este método é quando você está usando um limite não padrão. Conte uma olhada: regex101.com/r/j0nkeo/1 e regex101.com/r/4Ix7Dr/1
Enhardened em
1
@Enhardened - esse é um bom ponto, parece ser um problema com vários grupos de correspondência que se sobrepõem. Essa é uma situação em que você precisa olhar para trás.
rozza2058
1

Postagem muito antiga, mas gostaria de contribuir com algo que pode ajudar. Eu tentei exatamente da maneira indicada na pergunta e funciona, mas há um problema: a ordem das quantidades é importante. Considere isto:

#[a-f0-9]{6}|#[a-f0-9]{3}

Isso encontrará todas as ocorrências de códigos de cores hexadecimais (eles têm 3 ou 6 dígitos). Mas quando eu viro assim

#[a-f0-9]{3}|#[a-f0-9]{6}

ele encontrará apenas os de 3 dígitos ou os primeiros 3 dígitos dos de 6 dígitos. Isso faz sentido e um profissional da Regex pode perceber isso imediatamente, mas para muitos isso pode ser um comportamento peculiar. Existem alguns recursos avançados do Regex que podem evitar essa armadilha, independentemente da ordem, mas nem todo mundo está profundamente envolvido com os padrões do Regex.

DanDan
fonte