Есть ли веская причина использовать кванторы в регулярных выражениях Perl вместо того, чтобы просто повторять символ?

Я выполнял обзор кода для коллеги, и у него было регулярное выражение, которое выглядело так:

if ($value =~ /^\d\d\d\d$/) {
    #do stuff
}

Я сказал ему, что он должен изменить его на:

if ($value =~ /^\d{4}$/) {
    #do stuff
}

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

Мой вопрос: есть ли реальная польза для одного над другим?

Ответ 1

Они делают то же самое, так как практичность это вопрос предпочтения. Есть ли разница в производительности, так или иначе? Кто знает, но он, безусловно, незначителен.

Квантеры более полезны (и требуются), если длина шаблона не фиксирована, например \d{12,16}, \d{2,} и т.д.

Я предпочитаю \d{4}, который легче для моего мозга анализировать, чем \d\d\d\d

И что, если вы сопоставляете класс символов, а не просто цифру? [aeiouy0-9]{4} или [aeiouy0-9][aeiouy0-9][aeiouy0-9][aeiouy0-9]?

Ответ 2

Нет такой вещи, как абсолютная читаемость. Там, что люди могут индивидуально распознать, поэтому люди часто понимают свой код, а никто другой не может. Если он никогда не использует квантификаторы, он всегда будет думать, что квантификаторы трудно читать, потому что он никогда не узнает, чтобы их заманить.

Я чаще всего считаю, что люди говорят "более читабельны", когда они действительно означают "то, что я знаю уже" или "то, что я написал в первый раз". Однако это не обязательно в данном случае.

Абсолютный квантификатор, такой как {4}, проще всего указать и сообщить другим программистам. Кто хочет подсчитать количество \d вручную? Вы пишете код для других людей, чтобы читать, поэтому не делайте свою жизнь труднее.

Однако, возможно, вы пропустили ошибку в этом коде, потому что вы сосредоточились на проблеме квантификатора. Якорь $ разрешает новую строку в конце строки, и если появляется фанатик Perl Best Practices и слепо добавляет /xsm ко всем регулярным выражениям (болезненный опыт, который я видел более нескольких раз), $ позволяет получить еще более недействительный вывод. Вероятно, вы захотите вместо \z абсолютного привязки конца строки.

Не то, чтобы это произошло в вашем случае, но обзоры кода, как правило, превращаются в обзоры стиля или синтаксиса (потому что их легче заметить) и фактически не хватает места для проверки правильного и предполагаемого поведения и правильного дизайна. Часто проблемы стиля не стоит беспокоиться о том, чтобы рассмотреть все другие способы, которыми вы могли бы потратить время на улучшение кода.:)

Ответ 3

Сейчас я собираюсь обойти проблему читаемости.

Сначала давайте посмотрим, с чем сводится каждая версия.

perl -Mre=debug -e'/^\d{4}$/'
Compiling REx "^\d{4}$"
synthetic stclass "ANYOF[0-9][{unicode_all}]".
Final program:
   1: BOL (2)
   2: CURLY {4,4} (5)
   4:   DIGIT (0)
   5: EOL (6)
   6: END (0)
anchored ""$ at 4 stclass ANYOF[0-9][{unicode_all}] anchored(BOL) minlen 4 
Freeing REx: "^\d{4}$"
perl -Mre=debug -e'/^\d\d\d\d$/'
Compiling REx "^\d\d\d\d$"
Final program:
   1: BOL (2)
   2: DIGIT (3)
   3: DIGIT (4)
   4: DIGIT (5)
   5: DIGIT (6)
   6: EOL (7)
   7: END (0)
anchored ""$ at 4 stclass DIGIT anchored(BOL) minlen 4 
Freeing REx: "^\d\d\d\d$"

Теперь я посмотрю, насколько хорошо работает каждая версия.

#! /usr/bin/env perl
use Benchmark qw':all';

cmpthese( -10, {
  'loop' => sub{ 1234 =~ /^\d{4}$/ },
  'repeat' => sub{ 1234 =~ /^\d\d\d\d$/ }
});
           Rate   loop repeat
loop   890004/s     --   -10%
repeat 983825/s    11%     --

В то время как /^\d\d\d\d$/ работает последовательно быстрее, это происходит значительно быстрее. Который действительно просто оставляет его до удобочитаемости.


Допустим этот пример до крайности:

/^\d{32}$/;
/^\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d$/;

Я не думаю, что есть много людей, которые утверждают, что второй пример легче читать.

Если мы перейдем к другому пределу, первый стиль кажется совершенно лишним.

/^\d{1}$/;
/^\d$/;

Так что это действительно происходит, сколько повторений \d, прежде чем ваши предпочтения перейдут от повторения \d к использованию квантификатора.

Ответ 4

Любое повторение более 3 или 4 будет трудно подсчитать с первого взгляда. Я считаю это веской причиной. Кроме того, использование квантификатора является "более плотным" способом выражения повторяющейся информации. Для меня это похоже на разницу между копированием и вставкой кода "повторное использование" и написанием действительно многоразового кода.

Ответ 5

Лучше думать, что, когда он хочет найти набор из 10 букв, ему придется использовать квантификатор, а не повторять, лучше привыкнуть к правильному пути, кроме того, если он настаивает на использовании повторения для более крупных наборов символов, у кого-то будет некоторая проблема, пытаясь подсчитать их, что не понадобилось бы, если бы оно было отмечено квантификатором.

Ответ 6

{4} легче поддерживать, чем \d\d\d\d, потому что он масштабируется лучше. Например, если вам нужно изменить его в соответствии с 11 цифрами, вы можете просто изменить 4 на 11, вместо того, чтобы добавить 14 символов в ваше регулярное выражение.

Ответ 7

Как и многие вещи, речь идет о том, как далеко вы хотите его принять.

Настоящий пример.

Для сравнения:

my @lines = $header =~ m/([^\n\r]{13}|[^\n\r]+)/g; #split header into groups of up to 13 characters

к

my @lines = $header =~ m/([^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r][^\n\r]|[^\n\r]+)/g; #split into groups of up to 13 characters

Вы все еще можете найти трубу '|'?

Ответ 8

Я бы, вероятно, использовал любую форму, в зависимости от обстоятельств.

Пусть игнорирует сложность солома персональных классов символов, повторяющихся 96 раз в одной строке, и вместо этого фокусируется на хорошо написанном коде.

Рассмотрим:

$foo =~ m{
        (\d\d\d\d)
    [ ] (\d\d\d?)
    [ ] (\w\w)
}x;

Я использовал такой код для анализа данных от датчиков погоды. Я использую этот формат, потому что он точно соответствует документации производителя. Это очень хорошо подходит для форматов данных с фиксированной шириной, которые не вполне соответствуют обещаниям полей с фиксированной шириной (это на редкость распространено на практике).

Вы можете утверждать, что я должен помещать пробелы в отдельные строки или в ту же строку, что и предыдущее поле, а не в строке с последующим полем. Но это просто форматирование, и это действительно проблема для perltidy.

В других случаях я использовал такой код:

$foo =~ m{ 
        ( \d{4}   )
    [ ] ( \d{2,3} )
    [ ] ( \w{2}   )
}x;

Чтобы сохранить вышеприведенное для чтения, вам нужно добавить больше пробелов и немного поиграть с форматированием.

Второй стиль масштабируется со сложностью - добавление пользовательских классов символов и широких полей не нарушает читаемости.

Самое главное - быть последовательным в пределах данного регулярного выражения. IOW, никогда не делайте этого:

$foo =~ m{ 
        ( \d\d\d\d )
    [ ] ( \d{2,3}  )
    [ ] ( \w\w     )
}x;

В конечном счете код выполняет две функции. Наиболее известной функцией является то, что она сообщает компьютеру, что делать. Но самая важная, но в значительной степени упущенная функция кода - сообщить программисту по обслуживанию, что делает компьютер.

Ответ 9

О читаемости... некоторые программисты на Perl используют очень редкие функции, надеясь, что они будут читабельными, однако для этого требуется понимание этой редкой функции.

Есть много новичков регулярных выражений, которые не понимают, что такое {4}.

О преимуществах, второй может быть лучше, потому что он принимает меньше элементов массива в движке regexp. Если вы не настоящий программист, вы не будете оптимизировать производительность до наносекунд.