Как создать регулярное выражение, проверяющее римские цифры?

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

  • 4 цифры ИЛИ
  • значение вида XXXXXX-YY, где X — римские цифры от I до XXXIII, а YY — два латинских символа (A-Z)

person LA_    schedule 18.02.2012    source источник
comment
См. этот другой вопрос: stackoverflow.com/questions/267399/   -  person satoshi    schedule 18.02.2012
comment
@RobW, это может быть от 1 до 6 символов, поскольку ожидаемое значение от I до XXXIII (т.е. от 1 до 33).   -  person LA_    schedule 18.02.2012


Ответы (3)


Согласно требованиям, это возможные форматы римских чисел. Для удобочитаемости показано только максимальное количество X.

XXX III     (or: <empty>, I or II instead of III)
XX V       (or: IV, IX and X instead of IV)

Я предлагаю этот компактный шаблон:

/^(\d{4}|(?=[IVX])(X{0,3}I{0,3}|X{0,2}VI{0,3}|X{0,2}I?[VX])-[A-Z]{2})$/i

Объяснение:

^                Begin of string
(                Begin of group 1.
  \d{4}             4 digits

|                 OR

  (?=[IVX])         Look-ahead: Must be followed by a I, V or X
  (                  Begin of group 2.
     X{0,3}I{0,3}       = 0 1 2 3  + { 0 ; 10 ; 20 ; 30} (roman)
  |                  OR
     X{0,2}VI{0,3}      = 5 6 7 8  + { 0 ; 10 ; 20 }     (roman)
  |                  OR
     X{0,2}I?[VX]       = 4 9      + { 0 ; 10 ; 20 }     (roman)
  )                  End of group 2
  -[A-Z]{2}          Postfixed by a hyphen and two letters
)                 End of group 1.
$                End of string
person Rob W    schedule 18.02.2012
comment
Вау, это впечатляет! Однако требует гораздо больше размышлений, чем метод Regexp::Assemble. - person tchrist; 18.02.2012

Ну, часть, которая соответствует римской цифре между I и XXXIII:

(?:X(?:X(?:V(?:I(?:I?I)?)?|X(?:I(?:I?I)?)?|I(?:[VX]|I?I)?)?|V(?:I(?:I?I)?)?|I(?:[VX]|I?I)?)?|V(?:I(?:I?I)?)?|I(?:[VX]|I?I)?)

Как показывает это:

#!/usr/bin/env perl
use Regexp::Assemble;
use Roman;

my $ra = new Regexp::Assemble;

for my $num (1..33) {
    $ra->add(Roman($num));
} 

print $ra->re, "\n";
person tchrist    schedule 18.02.2012

function inputIsValid(value) {
    var r = /(^[0-9]{4}$)|(^(?:(?:[X]{0,2}(?:[I](?:[XV]?|[I]{0,2})?|(?:[V][I]{0,3})?))|(?:[X]{3}[I]{0,3}))\-[A-Z]{2}$)/ig;
    return value.match(r);
}

Это будет соответствовать либо 4-значному вводу, либо римскому числу (в диапазоне от 1 до 33), за которым следует тире и две буквы.

Чтобы объяснить регулярное выражение, ниже приведен расширенный источник с комментариями:

// Test for a 4-digit number
(                                       // Start required capturing group
    ^                                   // Start of string
    [0-9]{4}                            // Test for 0-9, exactly 4 times
    $                                   // End of string
)                                       // End required capturing group
|                                       // OR
// Test for Roman Numerals, 1 - 33, followed by a dash and two letters
(                                       // Start required capturing group
    ^                                   // Start of string
    (?:                                 // Start required non-capturing group
        // Test for 1 - 29
        (?:                             // Start required non-capturing group
            // Test for 10, 20, (and implied 0, although the Romans did not have a digit, or mathematical concept, for 0)
            [X]{0,2}                    // X, optionally up to 2 times
            (?:                         // Start required non-capturing group
                // Test for 1 - 4, and 9
                [I]                     // I, exactly once (I = 1)
                (?:                     // Start optional non-capturing group
                    // IV = 4, IX = 9
                    [XV]?               // Optional X or V, exactly once
                    |                   // OR
                    // II = 2, III = 3
                    [I]{0,2}            // Optional I, up to 2 times
                )?                      // End optional non-capturing group
                |                       // OR
                // Test for 5 - 8
                (?:                     // Start optional non-capturing group
                    [V][I]{0,3}         // Required V, followed by optional I, up to 3 times
                )?                      // End optional non-capturing group
            )                           // End required non-capturing group
        )                               // End required non-capturing group
        |                               // OR
        // Test for 30 - 33
        (?:                             // Start required non-capturing group
            // Test for 30
            [X]{3}                      // X exactly 3 times
            // Test for 1 - 3
            [I]{0,3}                    // Optional I, up to 3 times
        )                               // End required non-capturing group
    )                                   // End required non-capturing group
    // Test for dash and two letters
    \-                                  // Literal -, exactly 1 time
    [A-Z]{2}                            // Alphabetic character, exactly 2 times
    $                                   // End of string
)                                       // End required capturing group

Четырехзначное число и \-[A-Z]{2} в конце были (для меня) самоочевидными. Мой метод для римских цифр заключался в следующем:

  1. Откройте Excel. Заполните столбец цифрами от 1 до 33.
  2. Преобразуйте этот столбец в римские цифры (во всех 7 различных вариантах).
  3. Проверьте, не отличались ли какие-либо разновидности от 1-33 (они не отличались).
  4. Возился с перемещением римских цифр в минимальное количество уникальных моделей, которое ограничивало их до 33 (т. е. «тогда ты будешь считать до тридцати трех, не больше и не меньше. число подсчета должно быть тридцать три. Тридцать четыре ты не считаешь, и ты не считаешь тридцать два, за исключением того, что ты тогда перейдешь к тридцати трем. Тридцать пять - прямо».)
  5. Понял, что до тридцати девяти — это единый паттерн (^(([X]{0,3}([I]([XV]?|[I]{0,2})?|([V][I]{0,3})?)))$, изменено на группы захвата для большей наглядности).
  6. Изменен шаблон, чтобы разрешить до двадцати девяти.
  7. Добавил еще один, чтобы разрешить от тридцати до тридцати девяти.
  8. Создайте весь шаблон и протестируйте в RegexBuddy (бесценный инструмент для этого материала) против цифр 0–20 000 и римских цифр 1–150, за которыми следует «-AA».
  9. Схема сработала, поэтому я опубликовал ее (затем выпил еще одну чашку кофе и выдал себе «атта-бой» за выполнение того, что я считал прекрасным субботним утренним испытанием).

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

person pete    schedule 18.02.2012
comment
Как вы построили эти шаблоны и что со всеми лишними скобками? - person tchrist; 18.02.2012
comment
Нет, вообще-то, я имел в виду, почему ты написал [V][I]{0,3} вместо VI{0,3}. Кроме того, вы использовали неправильный символ комментария: регулярные выражения требуют #. О, подождите, это Javascript, где вам запрещено использовать режим /x или (?x). Javascript имеет худшие регулярные выражения из всех существующих языков. Просто ужасно. Тем не менее, плагин XRegExp немного помогает. - person tchrist; 18.02.2012
comment
Я построил этот шаблон вручную. Точный метод теперь в почте. - person pete; 18.02.2012
comment
О, потому что я склонен думать о классах символов при написании регулярных выражений. Я думаю, вы правы в том, что это, вероятно, могло бы быть проще. Первоначально я написал их как [vV][iI]{0,3}, а затем добавил переключатель без учета регистра (и удалил совпадения в нижнем регистре). - person pete; 18.02.2012