유효한 로마 숫자 만 정규식과 어떻게 일치합니까?


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}

물론 더 큰 숫자를 허용하려면 수천의 숫자 (0 포함) 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})(?=$)

(다른 대안은 길이가 미리 0이 아닌지 확인하는 것입니다).


12
M {0,3}이 아니어야합니까?
레몬

3
빈 문자열과 일치하지 않는 솔루션은 무엇입니까?
Facundo Casco

11
@Aashish : 로마인들이 생각할 힘이 있었을 때 MMMM, 올바른 방법이었습니다. 오버 바 표현은 핵심 제국이 무너진 지 오래되었습니다.
paxdiablo

2
@ paxdiablo 이것이 mmmcm 실패를 발견 한 방법입니다. 문자열 정규식 = "^ 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))-> Java의 MMMCM / MMMM에 대해 false로 평가됩니다.
amIT

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

23

실제로, 전제에 결함이 있습니다. 990 IS "XM"뿐만 아니라 "CMXC".

로마인들은 3 학년 교사보다 "규칙"에 대해 훨씬 덜 걱정했습니다. 합산하면 괜찮습니다. 따라서 "IIII"은 4의 "IV"와 같았으며 "IIM"은 998의 온도로 완전히 시원했습니다.

(만약 당신이 그걸 다루는데 어려움이 있다면 ... 1700 년대까지 영어 철자가 공식화되지 않았다는 것을 기억하십시오. 그때까지 독자가 알아낼 수있는 한 충분했습니다.)


8
물론, 멋지다. 그러나 나의 "엄격한 3 학년 교사"문법의 필요성은 내 생각에 훨씬 더 흥미로운 정규식 문제를 만들어 낸다.
Daniel Magliola

5
좋은 지적 야고보, 엄격한 저자이지만 용서하는 사람이어야합니다.
코린

@Corin : 일명 Postel의 견고성 원칙
jfs

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에서 작동해야합니다.

온라인 루비 데모 : http://rubular.com/r/KLPR1zq3Hj

온라인 전환 : http://www.onlineconversion.com/roman_numerals_advanced.htm


2
이유를 모르겠지만 MemoQ의 자동 번역 목록에서 주요 답변이 효과가 없었습니다. 그러나이 솔루션은 문자열 시작 / 끝 기호를 제외하고 수행합니다.
orlando2bjr

1
@ orlando2bjr 도와 드리겠습니다. 예,이 경우에는 주변 환경없이 자체적으로 숫자를 일치 시켰습니다. 텍스트에서 찾은 경우 ^ $를 제거해야합니다. 건배!
smileart

12

빈 문자열을 일치 방지하기 위해 당신은 패턴 네 번 반복하고 각 교체해야합니다 0A를을 1위해 차례로 및 계정 V, LD:

(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}))

이 경우 (이 패턴을 사용하기 때문에 ^그리고 $당신이 첫 번째 빈 행을 검사 나을 것하고 일치 귀찮게하지 않습니다). 단어 경계 를 사용 하는 경우 빈 단어와 같은 것이 없기 때문에 문제가 없습니다. (적어도 정규 표현식은 정의하지 않으며 철학을 시작하지 마십시오. 실용적입니다!)


내 자신의 특정 (실제) 경우에는 단어 끝에서 일치하는 숫자가 필요했으며 다른 방법은 없었습니다. "홍해 cl 및 그레이트 베리어 리프 cli " 와 같은 텍스트 가로 변환 된 일반 텍스트 문서에서 각주 번호를 제거 해야했습니다 the Red Seacl and the Great Barrier Reefcli. 하지만 난 여전히 같은 유효한 단어에 대한 문제가 있었다 Tahitifantastic로 세정하는 Tahitfantasti.


비슷한 문제가 있습니다! 이 남아있는 경우 그래서, 나는 항목 텍스트의 (왼쪽) 시작 부분에 당신의 정규식 (트림 기능 등) 청소해야합니다 ...하지만 더 간단한 : 항목을 사용하지 않을 M하거나 C또는 L그래서, 당신이해야합니까 간단한 정규식의 종류?
피터 크라우스

... 좋아, 여기 괜찮아 보인다 (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Peter Krauss

1
빈 문자열을 거부하기 위해 패턴을 반복 할 필요가 없습니다. 미리보기 어설 션을 사용할
jfs

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})/;

물론 모두 대소 문자를 구분하지 않아야합니다.

James Curran이 언급 한 옵션 (990 또는 999의 경우 XM 또는 IM, 400의 경우 CCCC 등)을 처리하도록이를 확장 할 수도 있습니다.

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

로 시작하면 주어진 로마 숫자 를 계산하고 유효성을 검사 하는 FSM을thousands hundreds tens units 쉽게 만들
jfs

당신은 무엇을 의미합니까 다행히, 숫자의 범위는 1..4999 또는 그 주변으로 제한된다 ? 누가 제한 했습니까?
SexyBeast

@SexyBeast : 더 큰 숫자는 물론 5,000에 대한 표준 로마 표기법이 없으므로 작동을 멈추고 작동을 멈추는 규칙.
Jonathan Leffler

왜 그렇게 믿는지 모르지만 로마 숫자는 수백만의 숫자를 나타낼 수 있습니다. en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel

@ AmbrosChapel : 내가 언급했듯이 더 큰 숫자는 말할 것도없이 5,000에 대한 (단일) 표준 표기법이 없습니다. 링크 된 Wikipedia 기사에 설명 된대로 여러 가지 발산 시스템 중 하나를 사용해야하며 오버 바, 언더 바 또는 역 C 등이있는 시스템의 정형 학 관련 문제에 직면하게됩니다. 사용중인 시스템과 그 의미; 사람들은 일반적으로 M 이상의 로마 숫자를 인식하지 못합니다. 그렇지 않으면 생각할 수도 있습니다. 그것은 나의 이전의 의견을지지하는 것이 나의 특권 인 것처럼 당신의 특권입니다.
Jonathan Leffler

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'

논리를 정말로 이해하고 싶은 사람들을 위해 diveintopython의 3 페이지에 대한 단계별 설명을 살펴보십시오 .

원래 솔루션과의 유일한 차이점은 M{0,4}'MMMM'이 유효한 로마 숫자가 아님을 발견했기 때문입니다 (오래된 로마인은 아마도 그 큰 수에 대해 생각하지 않았고 나와 동의하지 않을 것입니다). 당신이 오래된 로마인들에 동의하지 않는다면, 나를 용서하고 {0,4} 버전을 사용하십시오.


1
대답의 정규식은 빈 숫자를 허용합니다. 당신이 원하지 않는다면; lookahead assertion을 사용하여 빈 문자열을 거부 할 수 있습니다 (문자의 경우도 무시 함).
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

필자의 경우 로마 숫자의 모든 항목을 찾아서 텍스트 내에서 한 단어로 바꾸려고했기 때문에 줄의 시작과 끝을 사용할 수 없었습니다. @paxdiablo 솔루션은 길이가 0 인 일치 항목을 많이 발견했습니다. 나는 다음과 같은 표현으로 끝났다.

(?=\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})

내 최종 파이썬 코드는 다음과 같습니다.

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

Steven Levithan은 자신의 게시물 에서이 정규 표현식을 사용 하여 값을 "로만 표기"하기 전에 로마 숫자를 확인합니다.

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

0

빈 문자열을 다루지 않거나 lookaheads를 사용 하여이 문제를 해결하는 여러 답변을 보았습니다. 그리고 빈 문자열을 다루고 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 원하는 경우 허용 할 수 있습니다.

다음은 두 가지 온라인 데모가 선행하는 작업을 이해하는 데 도움이되는 시각화입니다.

Debuggex 데모

정규식 101 데모

정규식 시각화


0

이것은 Java 및 PCRE 정규식 엔진에서 작동하며 이제 최신 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

Jeremy와 Pax의 솔루션 문제는 "아무것도"일치하지 않는다는 것입니다.

다음 정규식은 하나 이상의 로마 숫자를 예상합니다.

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

6
(정말 이상한 정규식 구현을 사용하지 않는 한) 작동하지 않습니다-의 왼쪽 부분은 |빈 문자열과 모든 유효한 로마 숫자와 일치 할 수 있으므로 오른쪽은 완전히 중복됩니다. 예, 여전히 빈 문자열과 일치합니다.
먼지 iCE

"Jeremy와 Pax의 솔루션 문제는 ..."이 답변의 문제와 정확히 동일합니다. 예상되는 문제에 대한 해결책을 제시하려는 경우 테스트해야합니다. :-)
paxdiablo

나는이 빈 문자열을 가지고
Aminah Nuraini

-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
    {
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.