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

Думая о моей другой проблеме , я решил, что даже не могу создать регулярное выражение, которое будет соответствовать римским цифрам (не говоря уже о свободной от контекста грамматике, которая их будет генерировать)

Проблема заключается в совпадении только с действительными римскими цифрами. Например, 990 НЕ “XM”, это “CMXC”

Моя проблема в создании регулярного выражения для этого заключается в том, что для того, чтобы разрешить или не разрешить определенные символы, мне нужно оглянуться назад. Например, возьмем тысячи и сотни.

Я могу разрешить M {0,2} C? M (для разрешения 900, 1000, 1900, 2000, 2900 и 3000). Однако, если совпадение находится на CM, я не могу допускать, чтобы следующие символы были C или D (потому что я уже достиг 900).

Как я могу выразить это в регулярном выражении?
Если это просто не выражается в регулярном выражении, это выражается в контекстно-свободной грамматике?

    Пытаться:

    ^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$ 

    Разрушение:


    M{0,4}

    Это указывает раздел тысяч и в основном сдерживает его от 0 до 4000 . Это относительно просто:

      0:  matched by M{0} 1000: M matched by M{1} 2000: MM matched by M{2} 3000: MMM matched by M{3} 4000: MMMM matched by M{4} 

    (CM|CD|D?C{0,3})

    Немного сложнее, это для секции сотен и охватывает все возможности:

      0:  matched by D?C{0} (with D not there) 100: C matched by D?C{1} (with D not there) 200: CC matched by D?C{2} (with D not there) 300: CCC matched by D?C{3} (with D not there) 400: CD matched by CD 500: D matched by D?C{0} (with D there) 600: DC matched by D?C{1} (with D there) 700: DCC matched by D?C{2} (with D there) 800: DCCC matched by D?C{3} (with D there) 900: CM matched by CM 

    (XC|XL|L?X{0,3})

    Те же правила, что и предыдущий раздел, но для десятков мест:

      0:  matched by L?X{0} (with L not there) 10: X matched by L?X{1} (with L not there) 20: XX matched by L?X{2} (with L not there) 30: XXX matched by L?X{3} (with L not there) 40: XL matched by XL 50: L matched by L?X{0} (with L there) 60: LX matched by L?X{1} (with L there) 70: LXX matched by L?X{2} (with L there) 80: LXXX matched by L?X{3} (with L there) 90: XC matched by XC 

    (IX|IV|V?I{0,3})

    Это раздел единиц, обработка от 0 до 9 а также аналогичные предыдущим двум разделам (римские цифры, несмотря на кажущуюся странность, следуют некоторым логическим правилам, когда вы выясните, что они собой представляют):

     0:  matched by V?I{0} (with V not there) 1: I matched by V?I{1} (with V not there) 2: II matched by V?I{2} (with V not there) 3: III matched by V?I{3} (with V not there) 4: IV matched by IV 5: V matched by V?I{0} (with V there) 6: VI matched by V?I{1} (with V there) 7: VII matched by V?I{2} (with V there) 8: VIII matched by V?I{3} (with V there) 9: IX matched by IX 

    Собственно, ваша предпосылка испорчена. 990 IS “XM”, а также “CMXC”.

    Римляне были гораздо меньше обеспокоены «правилами», чем ваш учитель третьего classа. Пока он складывался, все было в порядке. Следовательно, «IIII» был так же хорош, как «IV» для 4. И «IIM» был совершенно classным на 998 год.

    (Если у вас есть проблемы с этим … Помните, что английские написания не были формализованы до 1700-х годов. До тех пор, пока читатель мог это понять, это было достаточно хорошо).

    Чтобы избежать сопоставления пустой строки, вам нужно будет повторить шаблон четыре раза и заменить каждый 0 на 1 по очереди и учитывать V , L и D :

     (M{1,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|M{0,4}(CM|C?D|D?C{1,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|M{0,4}(CM|CD|D?C{0,3})(XC|X?L|L?X{1,3})(IX|IV|V?I{0,3})|M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|I?V|V?I{1,3})) 

    В этом случае (потому что этот шаблон использует ^ и $ ), вам лучше было бы сначала проверить пустые строки и не мешать их сопоставлению. Если вы используете границы слов, то у вас нет проблемы, потому что нет такого понятия, как пустое слово. (По крайней мере, регулярное выражение не определяет одно, не начинайте философствовать, я прагматично здесь!)


    В моем собственном конкретном (реальном мире) случае мне нужны совпадающие цифры на концах слов, и я не нашел другого пути вокруг него. Мне нужно было очистить номера сносок от моего обычного текстового документа, где текст, такой как «Красное море и Великий Барьерный риф кли », был преобразован the Red Seacl and the Great Barrier Reefcli . Но у меня все еще были проблемы с действительными словами, такими как Tahiti и fantastic вычищена в Tahit и fantasti .

    К счастью, диапазон чисел ограничен 1..3999 или около того. Таким образом, вы можете создать кусок еды регулярного выражения.

      

    Каждая из этих частей будет иметь дело с капризами римской нотации. Например, используя нотацию Perl:

      = m/(CM|DC{0,3}|CD|C{1,3})?/; 

    Повторите и соберите.

    Добавлено : может быть сжата далее:

      = m/(C[MD]|D?C{0,3})/; 

    Так как предложение «D? C {0,3}» не может ничего сопоставить, нет необходимости в вопросительном знаке. И, скорее всего, круглые скобки должны быть не захватывающим типом – в Perl:

      = m/(?:C[MD]|D?C{0,3})/; 

    Конечно, все должно быть нечувствительным к регистру.

    Вы также можете расширить это, чтобы иметь дело с вариантами, упомянутыми Джеймсом Карраном (чтобы разрешить XM или IM для 990 или 999 и CCCC для 400 и т. Д.).

      = m/(?:[IXC][MD]|D?C{0,4})/; 

    Просто, чтобы сохранить его здесь:

     (^(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$) 

    Соответствует всем римским цифрам. Не заботится о пустых строках (требуется хотя бы одна римская цифра). Должен работать в PCRE, Perl, Python и Ruby.

    Онлайн-версия Ruby: http://rubular.com/r/KLPR1zq3Hj

    Онлайн-конверсия: http://www.onlineconversion.com/roman_numerals_advanced.htm

     import re pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$' if re.search(pattern, 'XCCMCI'): print 'Valid Roman' else: print 'Not valid Roman' 

    Для людей, которые действительно хотят понять логику, пожалуйста, взгляните на пошаговое объяснение на 3 страницах на diveintopython .

    Единственное отличие от оригинального решения (которое имело M{0,4} ) состоит в том, что я обнаружил, что «MMMM» не является действительной римской цифрой (также старые римляне, скорее всего, не подумали об этом огромном числе и не согласятся со мной). Если вы один из несогласных старых римлян, пожалуйста, простите меня и используйте версию {0,4}.

    Как указал Джереми и Пакс выше … ‘^ M {0,4} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0,3}) $ ‘должно быть решением, которое вам нужно …

    Конкретный URL, который должен был быть присоединен (IMHO), – http://thehazeltree.org/diveintopython/7.html

    Пример 7.8 – это короткая форма с использованием {n, m}

    Проблема решения Джереми и Пакса заключается в том, что он также соответствует «ничто».

    Следующее регулярное выражение ожидает по крайней мере одну римскую цифру:

     ^(M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|[IDCXMLV])$ 

    Стивен Левитан использует это регулярное выражение в своем посте, которое проверяет римские цифры до «дероманизации» значения:

     /^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/ 

    Я буду писать функции для своей работы для меня. В PowerShell есть две романные функции с числами.

     function ConvertFrom-RomanNumeral { <# .SYNOPSIS Converts a Roman numeral to a number. .DESCRIPTION Converts a Roman numeral - in the range of I..MMMCMXCIX - to a number. .EXAMPLE ConvertFrom-RomanNumeral -Numeral MMXIV .EXAMPLE "MMXIV" | ConvertFrom-RomanNumeral #> [CmdletBinding()] [OutputType([int])] Param ( [Parameter(Mandatory=$true, HelpMessage="Enter a roman numeral in the range I..MMMCMXCIX", ValueFromPipeline=$true, Position=0)] [ValidatePattern("^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$")] [string] $Numeral ) Begin { $RomanToDecimal = [ordered]@{ M = 1000 CM = 900 D = 500 CD = 400 C = 100 XC = 90 L = 50 X = 10 IX = 9 V = 5 IV = 4 I = 1 } } Process { $roman = $Numeral + " " $value = 0 do { foreach ($key in $RomanToDecimal.Keys) { if ($key.Length -eq 1) { if ($key -match $roman.Substring(0,1)) { $value += $RomanToDecimal.$key $roman = $roman.Substring(1) break } } else { if ($key -match $roman.Substring(0,2)) { $value += $RomanToDecimal.$key $roman = $roman.Substring(2) break } } } } until ($roman -eq " ") $value } End { } } function ConvertTo-RomanNumeral { <# .SYNOPSIS Converts a number to a Roman numeral. .DESCRIPTION Converts a number - in the range of 1 to 3,999 - to a Roman numeral. .EXAMPLE ConvertTo-RomanNumeral -Number (Get-Date).Year .EXAMPLE (Get-Date).Year | ConvertTo-RomanNumeral #> [CmdletBinding()] [OutputType([string])] Param ( [Parameter(Mandatory=$true, HelpMessage="Enter an integer in the range 1 to 3,999", ValueFromPipeline=$true, Position=0)] [ValidateRange(1,3999)] [int] $Number ) Begin { $DecimalToRoman = @{ Ones = "","I","II","III","IV","V","VI","VII","VIII","IX"; Tens = "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"; Hundreds = "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"; Thousands = "","M","MM","MMM" } $column = @{Thousands = 0; Hundreds = 1; Tens = 2; Ones = 3} } Process { [int[]]$digits = $Number.ToString().PadLeft(4,"0").ToCharArray() | ForEach-Object { [Char]::GetNumericValue($_) } $RomanNumeral = "" $RomanNumeral += $DecimalToRoman.Thousands[$digits[$column.Thousands]] $RomanNumeral += $DecimalToRoman.Hundreds[$digits[$column.Hundreds]] $RomanNumeral += $DecimalToRoman.Tens[$digits[$column.Tens]] $RomanNumeral += $DecimalToRoman.Ones[$digits[$column.Ones]] $RomanNumeral } End { } } 
    Давайте будем гением компьютера.