В чем разница между квадратными скобками и круглыми скобками в регулярном выражении?

101

Вот регулярное выражение, которое я создал для использования в JavaScript:

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

Вот еще один, предложенный членом моей команды.

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

Правило - проверить номер телефона:

  • Должно быть всего десять цифр.
  • Первое число должно быть любым из 7, 8 или 9.
Джаяпал Чандран
источник

Ответы:

124

Эти регулярные выражения эквивалентны (для целей сопоставления):

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

Объяснение:

  • (a|b|c)является регулярным выражением «ИЛИ» и означает «a, b или c», хотя наличие скобок, необходимых для OR, также фиксирует цифру. Чтобы быть строго эквивалентным, вы должны написать код, (?:7|8|9)чтобы сделать эту группу не захватывающей.

  • [abc]- это «класс символов», что означает «любой символ из a, b или c» (класс символов может использовать диапазоны, например [a-d]= [abcd])

Причина, по которой эти регулярные выражения похожи, заключается в том, что класс символов является сокращением для «или» (но только для отдельных символов). В качестве альтернативы вы также можете сделать что-то вроде того, (abc|def)что не переводится в класс символов.

Богемный
источник
30
(7|8|9)и [789]не эквивалентны, потому что первый захватывает, а второй нет. (?:7|8|9)было бы эквивалентно с другой стороны (я думаю, вы знаете это, конечно ...).
hochl
Я вижу это регулярное выражение: [<<|>>|\]\]|\[\[]. Из-за контекста я знаю, что регулярное выражение пытается сопоставить <<или >>или [[или ]]. Но из того, что вы сказали, оно должно совпадать <или >или [или ]. Если вы используете |между [], скобки ведут себя по-другому?
Daniel Kaplan
1
@DanielKaplan не используется |в классе символов [...], если только вы не хотите соответствовать самому символу вертикальной черты. Также дублирование символов в классе символов не имеет никакого эффекта - класс символов представляет собой список символов и будет соответствовать ровно одному из них. Думаю, вам нужна группа , в которой используются обычные круглые скобки:(<<|>>|\]\]|\[\[)
Bohemian
57

Совет вашей команды почти правильный, за исключением допущенной ошибки. Как только вы узнаете почему, вы никогда этого не забудете. Взгляните на эту ошибку.

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

Что это значит:

  • ^и $обозначает привязанные совпадения, в котором утверждается, что подшаблон между этими якорями является полным совпадением. Строка будет соответствовать только в том случае, если подшаблон соответствует всему, а не только разделу.
  • ()обозначает группу захвата .
  • 7|8|9означает соответствие любой из 7, 8или 9. Он делает это с чередованиями , что и |делает оператор конвейера - чередование чередований. При этом выполняется возврат между чередованиями: если первое чередование не совпадает, механизм должен вернуться до того, как позиция указателя переместится во время совпадения чередования, чтобы продолжить соответствие следующему чередованию; В то время как класс персонажей может продвигаться последовательно. Посмотрите это совпадение на движке регулярных выражений с отключенной оптимизацией:
Pattern: (r|f)at
Match string: carat

чередования

Pattern: [rf]at
Match string: carat

класс

  • \d{9}соответствует девяти цифрам. \d- сокращенный метасимвол, который соответствует любым цифрам.
/^[7|8|9][\d]{9}$/

Посмотрите, что он делает:

  • ^и также $обозначает привязанные совпадения.
  • [7|8|9]это класс персонажей . Любые символы из списка 7, |, 8, |, или 9могут быть подобраны, таким образом, |был добавлен в неправильно. Это соответствует без возврата.
  • [\d]- это класс символов, содержащий метасимвол \d. Между прочим, сочетание использования класса символов и одного метасимвола - плохая идея, поскольку уровень абстракции может замедлить сопоставление, но это всего лишь деталь реализации и применяется только к нескольким реализациям регулярных выражений. JavaScript не один, но он немного удлиняет подшаблон.
  • {9} указывает, что предыдущая единственная конструкция повторяется всего девять раз.

Оптимальное регулярное выражение /^[789]\d{9}$/, поскольку /^(7|8|9)\d{9}$/захватывает без необходимости, что приводит к снижению производительности для большинства реализаций регулярных выражений (является одним, учитывая, что вопрос использует ключевое слово varв коде, вероятно, это JavaScript). Использованиекоторый работает на PCRE для сопоставления preg, оптимизирует отсутствие отслеживания с возвратом, однако мы тоже не на PHP, поэтому использование классов []вместо чередований |дает бонус к производительности, поскольку сопоставление не выполняется с возвратом, и, следовательно, как совпадение, так и сбой происходит быстрее, чем использование вашего предыдущее регулярное выражение.

Unihedron
источник
6
просто из интереса, из какой программы этот скриншот?
Мистер Тайный гость,
12

Первые два примера действуют по-разному, если вы их чем-то ЗАМЕНЯЕТЕ. Если вы соответствуете этому:

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

вы бы заменили 7, 8 или 9 пустой строкой.

Если вы соответствуете этому

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

вы будете заменить 7или 8или 9ИЛИ ВЕРТИКАЛЬНЫЙ BAR !!!! пустой строкой.

Я обнаружил это на собственном горьком опыте.

Шейла
источник
6
Добро пожаловать в SO! Замена или сопоставление - это просто неправильно. Многие люди совершают эту ошибку, и обычно им это сходит с рук - иногда годами - потому что их входные строки никогда не содержат pipe ( |).
Алан Мур