Як ви співпадаєте лише з дійсними римськими цифрами з регулярним виразом?


165

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

Проблема - відповідність лише дійсних римських цифр. Наприклад, 990 НЕ "XM", це "CMXC"

Моя проблема в створенні регулярного вираження для цього полягає в тому, що для того, щоб дозволити чи не дозволити певні символи, мені потрібно озирнутися назад. Візьмемо, наприклад, тисячі і сотні.

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

Як я можу це виразити в регулярному виразі?
Якщо це просто не виражається в регулярному виразі, чи це виражається в безконтекстній граматиці?

Відповіді:


328

Для цього можна використовувати наступний регулярний вираз:

^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: <empty>  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}

Звичайно, ви можете використовувати щось на зразок, M*щоб дозволити будь-яку кількість (включаючи нуль) тисяч, якщо ви хочете дозволити більші числа.

Далі (CM|CD|D?C{0,3}), трохи складніше, це для сотні розділів і охоплює всі можливості:

  0: <empty>  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: <empty>  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: <empty>  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

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

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

(інша альтернатива полягає у тому, щоб попередньо перевірити, чи довжина не дорівнює нулю).


12
Чи не повинно бути M {0,3}?
лимон

3
будь-яке рішення, щоб уникнути відповідності порожній рядку?
Facundo Casco

11
@Aashish: Коли римляни були змушені враховувати, це MMMMбув правильний шлях. Представлення овербардів з'явилося задовго після того, як основна імперія розпалася.
paxdiablo

2
@paxdiablo ось так я виявив невдачу mmmcm. String regx = "^ M {0,3} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0, 3}) $ "; if (input.matches (regx)) -> це визначає значення false для MMMCM / MMMM в Java.
amIT

2
/^M{0,3}(?:C[MD]|D?C{0,3})(?:X[CL]|L?X{0,3})(?:I[XV]|V?I{0,3})$/i
Криссов

23

Насправді, ваше приміщення є помилковим. 990 IS "XM", а також "CMXC".

Римляни були набагато менше стурбовані "правилами", ніж ваш вчитель третього класу. Поки він додався, все було нормально. Отже, "IIII" був таким же хорошим, як "IV" для 4. А "IIM" був абсолютно крутим за 998 рік.

(Якщо у вас виникли проблеми з цим ... Пам’ятайте, англійські написання не були формалізовані до 1700-х років. До тих пір, поки читач міг це зрозуміти, це було досить добре).


8
Звичайно, це здорово. Але моя синтаксична потреба в "строгому вчителі третього класу" робить набагато цікавішою проблему з регулярними
виразами

5
Добре, що Джеймс, треба бути суворим автором, але читачем, що прощає.
Корін


13

Просто, щоб зберегти його тут:

(^(?=[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


2
Не знаю чому, але головна відповідь не спрацювала для мене в списках автоперекладу в MemoQ. Однак це рішення є - виключаючи символи початку та кінця рядків.
orlando2bjr

1
@ orlando2bjr радий допомогти. Так, у цьому випадку я підбирав номер самостійно, без оточення. Якщо ви шукаєте його в тексті, обов'язково потрібно буде видалити ^ $. Ура!
смайлик

12

Щоб уникнути зіставлень порожнього рядка , яку ви повинні будете повторювати малюнок на чотири рази , і замінити кожен 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.


У мене є аналогічна проблема (!): Зробити «ліву обробку» залишкового / залишкового римського номера списку елементів (HTML OL типу I або i). Таким чином, коли є інші, мені потрібно , щоб очистити (як функція обрізання) з регулярним виразом на початку (зліва) елемент-тексті ... Але більш простий: пункти ніколи не використовувати Mабо Cчи L, так, у вас є це вид спрощеного регулярного вираження?
Пітер Краусс

... гаразд, тут здається нормально (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Пітер Краус

1
вам не потрібно повторювати шаблон, щоб відхиляти порожні рядки. Ви можете використати твердження
lookahead

7

На щастя, діапазон чисел обмежений 1..3999 або більше. Таким чином, ви можете створити шматочки страви з регулярними виразками.

<opt-thousands-part><opt-hundreds-part><opt-tens-part><opt-units-part>

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

<opt-hundreds-part> = m/(CM|DC{0,3}|CD|C{1,3})?/;

Повторіть і зберіть.

Додано : <opt-hundreds-part>Можна додатково стиснути:

<opt-hundreds-part> = m/(C[MD]|D?C{0,3})/;

Оскільки пункт "D? C {0,3}" не може відповідати нічого, в питаннях немає потреби в знаку питання. І, швидше за все, дужки повинні бути типом, що не захоплює - в Perl:

<opt-hundreds-part> = m/(?:C[MD]|D?C{0,3})/;

Звичайно, це також повинно бути нечутливим до регістру.

Ви також можете розширити це для вирішення варіантів, згаданих Джеймсом Курраном (дозволити XM або IM для 990 або 999, а CCCC для 400 і т.д.).

<opt-hundreds-part> = m/(?:[IXC][MD]|D?C{0,4})/;


Що ви маєте на увазі під щастям, діапазон чисел обмежений 1..3999 або звідси ? Хто її обмежив?
SexyBeast

@SexyBeast: Немає жодної стандартної римської нотації на 5000, не кажучи вже про більшу кількість, тому закономірності, які працюють до цього часу, перестають працювати.
Джонатан Леффлер

Не впевнений, чому ви вважаєте це, але римські цифри можуть представляти числа в мільйони. en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel

@AmbroseChapel: Як я вже зазначив, не існує жодної (одиничної) стандартної позначення для 5000, не кажучи вже про більші числа. Вам доведеться використовувати одну з кількох розбіжних систем, як описано в статті Вікіпедії, на яку ви посилаєтесь, і ви стикаєтесь з проблемами з орфографією для системи з перекладинами, підрисами або перевернутим С і т. Д. І вам доведеться комусь пояснити, що система, яку ви використовуєте, і що це означає; люди взагалі не будуть розпізнавати римські цифри понад М. Ви можете думати інакше; це ваша прерогатива, так само як і моя прерогатива стояти за попередніми коментарями.
Джонатан Леффлер

7
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}.


1
регулярний вираз у відповіді допускає порожні цифри. Якщо ви цього не хочете; ви можете використовувати твердження lookahead , щоб відхилити порожні рядки (він також ігнорує регістр букв).
jfs

2

Я відповідаю на це запитання Регулярне вираження в Python для римських чисел тут,
оскільки воно було позначене як точний дублікат цього питання.

Це може бути схожим за назвою, але це специфічне регекс-питання / проблема,
як це видно з цієї відповіді на це питання.

Елементи, що шукаються, можна об'єднати в одне чергування і потім
укласти в групу захоплення, яка буде внесена до списку з
функцією findall () .
Робиться так:

>>> import re
>>> target = (
... r"this should pass v" + "\n"
... r"this is a test iii" + "\n"
... )
>>>
>>> re.findall( r"(?m)\s(i{1,3}v*|v)$", target )
['v', 'iii']

Зміни регулярного вираження для розбиття та фіксації лише цифр:

 (?m)
 \s 
 (                     # (1 start)
      i{1,3} 
      v* 
   |  v
 )                     # (1 end)
 $

1

Як вказували Джеремі та Пакс вище ... '^ 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}


1

У моєму випадку я намагався знайти і замінити всі виникнення римських чисел одним словом всередині тексту, тому я не міг використати початок і кінець рядків. Тож рішення @paxdiablo знайшло багато збігів нульової довжини. Я закінчив таке вираз:

(?=\b[MCDXLVI]{1,6}\b)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})

Мій остаточний код Python був таким:

import re
text = "RULES OF LIFE: I. STAY CURIOUS; II. NEVER STOP LEARNING"
text = re.sub(r'(?=\b[MCDXLVI]{1,6}\b)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})', 'ROMAN', text)
print(text)

Вихід:

RULES OF LIFE: ROMAN. STAY CURIOUS; ROMAN. NEVER STOP LEARNING

0

Стівен Левітан використовує цей регулярний вираз у своєму дописі, який підтверджує римські цифри до "дероманізації" значення:

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

0

Я бачив кілька відповідей, які не охоплюють порожні рядки або використовують шляхи пошуку для вирішення цього питання. І я хочу додати нову відповідь, яка охоплює порожні рядки і не використовує lookahead. Регекс є наступним:

^(I[VX]|VI{0,3}|I{1,3})|((X[LC]|LX{0,3}|X{1,3})(I[VX]|V?I{0,3}))|((C[DM]|DC{0,3}|C{1,3})(X[LC]|L?X{0,3})(I[VX]|V?I{0,3}))|(M+(C[DM]|D?C{0,3})(X[LC]|L?X{0,3})(I[VX]|V?I{0,3}))$

Я дозволяю нескінченно M, M+але, звичайно, хтось може змінити, M{1,4}щоб дозволити лише 1 або 4 за бажанням.

Нижче представлена ​​візуалізація, яка допомагає зрозуміти, що вона робить, перед якими дві демонстрації в Інтернеті:

Демонстраційна демонстрація

Регекс 101 демо

Регулярна візуалізація виразів


0

Це працює в Java і PCRE regex двигунах і тепер має працювати в останньому JavaScript, але може працювати не у всіх контекстах.

(?<![A-Z])(M*(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))(?![A-Z])

Перша частина - це жорстокий негативний погляд позаду. Але для логічних цілей це найлегше зрозуміти. В основному, перший (?<!)говорить: не збігайтесь із серединою, ([MATCH])якщо перед серединою надходять букви, ([MATCH])а останній (?!)- не відповідає середині, ([MATCH])якщо після нього надходять листи.

Середина ([MATCH])- це лише найчастіше використовуваний регулярний вираз для відповідності послідовності римських чисел. Але тепер ви не хочете відповідати цьому, якщо навколо нього є якісь букви.

Побачте самі. https://regexr.com/4vce5


-1

Проблема рішення від Джеремі та Пакса полягає в тому, що воно також відповідає "нічого".

Наступний вираз передбачає хоча б одну римську цифру:

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

6
це не буде працювати (якщо ви не використовуєте дуже дивну реагекс-схему) - ліва частина |може відповідати порожній рядку та всім дійсним римським цифрам, тому права частина є абсолютно зайвою. і так, вона все ще відповідає порожній рядку.
Брудне iCE

"Проблема рішення від Джеремі та Пакса" ... точно така ж, як проблема, яку має ця відповідь. Якщо ви збираєтесь запропонувати рішення передбачуваної проблеми, ви, ймовірно, повинні її перевірити. :-)
paxdiablo

У мене з цим порожній рядок
Аміна Нурайні

-2

Я би писав функції для своєї роботи для мене. Ось дві функції римської цифри в 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
    {
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.