Quelle est la différence entre les crochets et les parenthèses dans une expression régulière?

101

Voici une expression régulière que j'ai créée pour l'utiliser en JavaScript:

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

En voici une autre suggérée par le membre de mon équipe.

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

La règle est de valider un numéro de téléphone:

  • Il ne devrait y avoir que dix nombres.
  • Le premier nombre est censé être l'un des 7, 8 ou 9.
Jayapal Chandran
la source

Réponses:

124

Ces expressions régulières sont équivalentes (à des fins de correspondance):

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

L'explication:

  • (a|b|c)est une expression régulière "OU" et signifie "a ou b ou c", bien que la présence de crochets, nécessaire pour le OU, capture également le chiffre. Pour être strictement équivalent, vous coderiez (?:7|8|9)pour en faire un groupe non capturant.

  • [abc]est une "classe de caractères" qui signifie "tout caractère de a, b ou c" (une classe de caractères peut utiliser des plages, par exemple [a-d]= [abcd])

La raison pour laquelle ces expressions régulières sont similaires est qu'une classe de caractères est un raccourci pour un "ou" (mais uniquement pour des caractères uniques). Dans une alternative, vous pouvez également faire quelque chose comme (abc|def)qui ne se traduit pas par une classe de caractères.

Bohème
la source
30
(7|8|9)et [789]ne sont pas équivalents, car le premier est la capture, le second non. (?:7|8|9)serait équivalent par contre (je suppose que vous le savez bien sur ...).
hochl
Je vois ce regex: [<<|>>|\]\]|\[\[]. En raison du contexte, je sais que regex essaie de faire correspondre <<ou >>ou [[ou ]]. Mais d'après ce que vous avez dit, cela devrait correspondre à <ou >ou [ou ]. Si vous utilisez |entre [], les parenthèses se comportent-elles différemment?
Daniel Kaplan
1
@DanielKaplan ne l'utilise pas |dans une classe de caractères [...], à moins que vous ne vouliez faire correspondre le caractère pipe lui-même. De plus, la duplication de caractères dans une classe de caractères n'a aucun effet - une classe de caractères est une liste de caractères et correspondra exactement à l'un d'entre eux. Je suppose que vous voulez un groupe , qui utilise des parenthèses rondes normales:(<<|>>|\]\]|\[\[)
Bohème
57

Les conseils de votre équipe sont presque corrects, sauf pour l'erreur qui a été commise. Une fois que vous aurez découvert pourquoi, vous ne l'oublierez jamais. Jetez un œil à cette erreur.

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

Ce que cela fait:

  • ^ et $ désigne des correspondances ancrées, ce qui affirme que le sous-modèle entre ces ancres est la correspondance entière. La chaîne ne correspondra que si le sous-modèle correspond à l'intégralité de celui-ci, pas seulement à une section.
  • ()désigne un groupe de capture .
  • 7|8|9désigne correspondant à l' une des 7, 8ou 9. Il le fait avec des alternances , ce que fait l'opérateur de tuyauterie |- en alternant entre les alternances. Ce retour en arrière entre les alternances: si la première alternance n'est pas appariée, le moteur doit revenir avant l'emplacement du pointeur déplacé pendant le match de l'alternance, pour continuer à faire correspondre l'alternance suivante; Alors que la classe de caractères peut avancer séquentiellement. Voir cette correspondance sur un moteur regex avec les optimisations désactivées:
Pattern: (r|f)at
Match string: carat

alternances

Pattern: [rf]at
Match string: carat

classe

  • \d{9}correspond à neuf chiffres. \dest un métacaractère abrégé, qui correspond à tous les chiffres.
/^[7|8|9][\d]{9}$/

Regardez ce qu'il fait:

  • ^et $désigne également les correspondances ancrées.
  • [7|8|9]est une classe de caractères . Tous les caractères de la liste 7, |, 8, |ou 9peuvent être adaptés, ainsi l' |a été ajouté à tort. Cela correspond sans retour en arrière.
  • [\d]est une classe de caractères qui habite le métacaractère \d. La combinaison de l'utilisation d'une classe de caractères et d'un seul métacaractère est une mauvaise idée, d'ailleurs, car la couche d'abstraction peut ralentir la correspondance, mais ce n'est qu'un détail d'implémentation et ne s'applique qu'à quelques implémentations de regex. JavaScript n'en est pas un, mais il allonge légèrement le sous-modèle.
  • {9} indique que la construction unique précédente est répétée neuf fois au total.

La regex optimale est /^[789]\d{9}$/, parce que/^(7|8|9)\d{9}$/ captures inutilement ce qui impose une diminution des performances sur la plupart des implémentations de regex (se trouve en être un, étant donné que la question utilise un mot-clé vardans le code, il s'agit probablement de JavaScript). L'utilisation dequi fonctionne sur PCRE pour la correspondance preg optimisera le manque de retour en arrière, mais nous ne sommes pas non plus en PHP, donc utiliser des classes []au lieu d'alternations |donne un bonus de performance car le match ne revient pas en arrière, et donc à la fois correspond et échoue plus rapidement que d'utiliser votre expression régulière précédente.

Unièdre
la source
6
juste par intérêt, de quel programme provient cette capture d'écran?
Mr Mystery Guest
12

Les 2 premiers exemples agissent très différemment si vous les REMPLACEZ par quelque chose. Si vous correspondez à ceci:

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

vous remplaceriez 7 ou 8 ou 9 par la chaîne vide.

Si vous correspondez à cela

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

vous allez remplacer 7ou 8ou 9OU LA BARRE VERTICALE !!!! par la chaîne vide.

Je viens de découvrir cela à la dure.

Sheila
la source
6
Bienvenue à SO! Remplacer ou assortir, c'est tout simplement faux. Beaucoup de gens font cette erreur, et ils s'en sortent généralement - pendant des années, parfois - parce que leurs chaînes d'entrée ne contiennent jamais de pipe ( |).
Alan Moore