Как вы сопоставляете только действительные римские цифры с регулярным выражением?
Думая о моей другой проблеме , я решил, что даже не могу создать регулярное выражение, которое будет соответствовать римским цифрам (не говоря уже о свободной от контекста грамматике, которая их будет генерировать)
Проблема заключается в совпадении только с действительными римскими цифрами. Например, 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 { } }