Qual é a diferença entre colchetes e parênteses em uma regex?

101

Aqui está uma expressão regular que criei para usar em JavaScript:

var reg_num = /^(7|8|9)\d{9}$/

Aqui está outra sugestão de um membro da minha equipe.

var reg_num = /^[7|8|9][\d]{9}$/

A regra é validar um número de telefone:

  • Deve ser de apenas dez números.
  • O primeiro número deve ser 7, 8 ou 9.
Jayapal Chandran
fonte

Respostas:

124

Essas regexes são equivalentes (para fins de correspondência):

  • /^(7|8|9)\d{9}$/
  • /^[789]\d{9}$/
  • /^[7-9]\d{9}$/

A explicação:

  • (a|b|c)é uma regex "OR" e significa "a ou b ou c", embora a presença de colchetes, necessários para o OR, também capture o dígito. Para ser estritamente equivalente, você codificaria (?:7|8|9)para torná-lo um grupo de não captura.

  • [abc]é uma "classe de caracteres" que significa "qualquer caractere de a, b ou c" (uma classe de caracteres pode usar intervalos, por exemplo [a-d]= [abcd])

A razão pela qual essas regexes são semelhantes é que uma classe de caractere é uma abreviação para um "ou" (mas apenas para caracteres únicos). Em uma alternância, você também pode fazer algo como o (abc|def)que não se traduz em uma classe de personagem.

Boêmio
fonte
30
(7|8|9)e [789]não são equivalentes, porque o primeiro captura, o último não. (?:7|8|9)seria equivalente, por outro lado (acho que você sabe disso, é claro ...).
hochl
Eu estou vendo isso regex: [<<|>>|\]\]|\[\[]. Por causa do contexto, sei que a regex está tentando corresponder a <<ou >>ou [[ou ]]. Mas, pelo que você disse, deve ser igual <ou >ou [ou ]. Se você usar |entre [], os colchetes se comportam de maneira diferente?
Daniel Kaplan
1
@DanielKaplan não usa |dentro de uma classe de caractere [...], a menos que você queira corresponder à barra vertical em si. Além disso, duplicar chars em uma classe de personagem não tem efeito - uma classe de personagem é uma lista de personagens e irá corresponder exatamente a um deles. Meu palpite é que você quer um grupo , que usa colchetes normais:(<<|>>|\]\]|\[\[)
Boêmio
57

O conselho de sua equipe é quase correto, exceto pelo erro que foi cometido. Depois de descobrir o porquê, você nunca mais esquecerá. Dê uma olhada neste erro.

/^(7|8|9)\d{9}$/

O que isso faz:

  • ^e $denota correspondências ancoradas, que afirmam que o subpadrão entre essas âncoras é a correspondência inteira. A string só corresponderá se o subpadrão corresponder à totalidade, não apenas a uma seção.
  • ()denota um grupo de captura .
  • 7|8|9indica a correspondência de qualquer um dos 7, 8ou 9. Ele faz isso com alternâncias , que é o que o operador de tubo |faz - alternando entre alternâncias. Isso retrocede entre as alternâncias: Se a primeira alternância não for correspondida, o motor deve retornar antes que a localização do ponteiro se mova durante a correspondência da alternância, para continuar correspondendo à próxima alternância; Considerando que a classe de personagem pode avançar sequencialmente. Veja esta correspondência em um mecanismo regex com otimizações desativadas:
Pattern: (r|f)at
Match string: carat

alternâncias

Pattern: [rf]at
Match string: carat

classe

  • \d{9}corresponde a nove dígitos. \dé um metacaractere abreviado, que corresponde a qualquer dígito.
/^[7|8|9][\d]{9}$/

Veja o que ele faz:

  • ^e $denota correspondências ancoradas também.
  • [7|8|9]é uma classe de personagem . Quaisquer caracteres a partir da lista 7, |, 8, |, ou 9podem ser combinados, assim o |foi adicionado na forma incorreta. Isso corresponde sem retrocesso.
  • [\d]é uma classe de personagem que habita o metacaractere \d. A combinação do uso de uma classe de caractere e um único metacaractere é uma má ideia, a propósito, já que a camada de abstração pode retardar a correspondência, mas este é apenas um detalhe de implementação e se aplica apenas a algumas implementações de regex. JavaScript não é um deles, mas torna o subpadrão um pouco mais longo.
  • {9} indica que a única construção anterior é repetida nove vezes no total.

O regex ideal é /^[789]\d{9}$/, porque /^(7|8|9)\d{9}$/captura desnecessariamente, o que impõe uma diminuição de desempenho na maioria das implementações de regex (acontece de ser um, considerando que a questão usa palavra-chave varno código, provavelmente é JavaScript). O uso deque roda em PCRE para correspondência preg irá otimizar a falta de retrocesso, no entanto, não estamos em PHP também, então usar classes em []vez de alternações |dá bônus de desempenho, pois a correspondência não retrocede e, portanto, coincide e falha mais rápido do que usar seu expressão regular anterior.

Unihedron
fonte
6
só por interesse, de qual programa é essa captura de tela?
Sr. Mystery Guest
12

Os primeiros 2 exemplos atuam de maneira muito diferente se você os estiver SUBSTITUINDO por algo. Se você corresponder a este:

str = str.replace(/^(7|8|9)/ig,''); 

você substituiria 7 ou 8 ou 9 pela string vazia.

Se você combinar neste

str = str.replace(/^[7|8|9]/ig,''); 

você vai substituir 7ou 8ou 9OU A BARRA VERTICAL !!!! pela string vazia.

Eu só descobri isso da maneira mais difícil.

Sheila
fonte
6
Bem-vindo ao SO! Substituir ou combinar, é simplesmente errado. Muitas pessoas cometem esse erro e geralmente se safam - por anos, às vezes - porque suas strings de entrada nunca contêm um pipe ( |).
Alan Moore de