有効なローマ数字のみを正規表現とどのように照合しますか?


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}数千人のセクションを指定し、基本的に間にそれを抑制する04000。それは比較的簡単です:

   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

3番目に、(XC|XL|L?X{0,3})前のセクションと同じルールに従いますが、10の位についてです。

 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})、ユニットセクションは前の2つのセクションと同様に処理さ09ます(奇妙に見えるにもかかわらず、ローマ数字は、それらが何であるかを理解したら、いくつかの論理的なルールに従います)。

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
@アーシシュ:ローマ人が考慮される力だったとき、それMMMMは正しい方法でした。中核帝国がバラバラに崩壊してからずっと後、オーバーバーの表現が登場した。
paxdiablo 2013

2
@paxdiabloこれは私がmmmcmが失敗することを発見した方法です。文字列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))->これは、JavaのMMMCM / MMMMではfalseと評価されます。
amIT 2014

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"。

ローマ人はあなたの3年生の教師よりも「ルール」についてはるかに心配していませんでした。足し合わせさえすれば大丈夫でした。したがって、「IIII」は4の「IV」と同じくらい良かったです。「IIM」は998は完全にクールでした。

(これに対処するのに問題がある場合...英語のスペルは1700年代まで正式化されていなかったことを覚えておいてください。それまでは、読者が理解できる限り、それで十分でした)。


8
かしこまりました。しかし、私の「厳格な3年生の教師」の構文の必要性は、私の意見では、はるかに興味深い正規表現の問題を
引き起こし

5
良い点ジェームズ、厳密な著者であるが寛容な読者であるべきです。
Corin


13

ここに保存するだけです:

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

すべてのローマ数字に一致します。空の文字列は関係ありません(少なくとも1つのローマ数字が必要です)。PCRE、Perl、Python、Rubyで動作するはずです。

オンラインRubyデモ:http : //rubular.com/r/KLPR1zq3Hj

オンライン変換:http : //www.onlineconversion.com/roman_numerals_advanced.htm


2
理由はわかりませんが、MemoQの自動翻訳リストでは主な回答が機能しませんでした。ただし、このソリューションでは、文字列の開始/終了記号は除外されます。
orlando2bjr 2017

1
@ orlando2bjrは喜んでお手伝いします。ええ、この場合、私は周囲に関係なく、それ自体で数を照合していました。テキストで探している場合は、^ $を削除する必要があります。乾杯!
smileart

12

空の文字列にマッチするのを避けるために、あなたはパターンを4回繰り返し、それぞれ置き換える必要があります0との1ために順番に、アカウントVLおよび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}))

この場合(このパターンは^andを使用しているため$)、最初に空の行をチェックし、それらを照合する手間を省いた方がよいでしょう。単語の境界を使用している場合、空の単語などは存在しないため、問題はありません。(少なくとも正規表現はそれを定義していません。哲学を始めないでください。私はここでは実用的です!)


私自身の特定の(現実の世界)ケースでは、単語の末尾に一致番号が必要でしたが、他に方法はありませんでした。「紅海clとグレートバリアリーフcli」などのテキストがに変換されたプレーンテキストドキュメントから脚注番号を削除する必要がありましたthe Red Seacl and the Great Barrier Reefcli。しかし、私はまだのような有効な言葉で問題を抱えていたTahitifantasticに擦っているTahitfantasti


私は同様の問題(!)を持っています:アイテムリストの残り/残りのローマ数字(タイプIまたはiのHTML OL)の「左トリム」を実行します。だから、残っているときは、item-textの先頭(左)で正規表現を使って(トリム関数のように)クリーンアップする必要があります...しかし、もっと単純です:アイテムはMor Cやを使用しないLので、これはありますか?一種の単純化された正規表現?
Peter Krauss、

...大丈夫、ここでは大丈夫(!)です(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
ピータークラウス14

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

からはthousands hundreds tens units与えられたローマ数字を計算して検証するFSMを
jfs

あなたは何を意味するのです幸いなことに、番号の範囲を1..3999またはその付近に限定されていますか?誰がそれを制限したのですか?
SexyBeast 2017

@SexyBeast:大きな数字はもちろんのこと、5,000の標準的なローマ表記はありません。そのため、規則性が高まってから機能しなくなります。
Jonathan Leffler 2017

なぜあなたはそれを信じるかはわかりませんが、ローマ数字は数百万の数を表すことができます。en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel

@AmbroseChapel:すでに述べたように、5,000の(単一の)標準表記はありません。リンク先のWikipediaの記事で概説されているように、いくつかの異なるシステムの1つを使用する必要があり、オーバーバー、アンダーバー、または逆Cなどのシステムの正書法で問題が発生します。使用しているシステムとその意味; 人々は、一般的に、Mを超えるローマ数字を認識しません。私の以前のコメントを支持するのが私の特権であるのと同じように、それはあなたの特権です。
Jonathan Leffler、2018

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
答えの正規表現は空の数字を許可します。あなたがそれを望まないなら; 先読みアサーションを使用して、空の文字列を拒否できます(文字の大文字と小文字の区別も無視されます)。
jfs

2

イムは、この質問に答えるローマ数字のためにPythonで正規表現をここでは
それがこの質問の正確な複製としてマークされていたので。

名前は似ているかもしれませんが
、この質問に対するこの回答でわかるように、これは特定の正規表現の質問/問題です。

探しているアイテムを1つの代替に組み合わせてから
、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

私の場合、ローマ数字のすべての出現箇所をテキスト内で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

Steven Levithanは、彼の投稿でこの正規表現を使用して、値を「逆化」する前にローマ数字を検証しています。

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

0

空の文字列をカバーしない、または先読みを使用してこれを解決する複数の回答を見てきました。そして、空の文字列をカバーし、先読みを使用しない新しい回答を追加したいと思います。正規表現は次のとおりです。

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

私は無限を許可しMM+いますが、もちろん、誰かが変更しM{1,4}て、必要に応じて1または4のみを許可することもできます。

以下は、それが何をしているのかを理解するのに役立つ視覚化です。

Debuggexデモ

Regex 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のソリューションの問題は、「何も」にも一致しないことです。

次の正規表現では、少なくとも1つのローマ数字が必要です。

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

6
これは機能しません(非常に奇妙な正規表現の実装を使用している場合を除きます)。の左側|は空の文字列とすべての有効なローマ数字に一致する可能性があるため、右側は完全に冗長です。はい、それでも空の文字列に一致します。
DirtY iCE 2011

「JeremyとPaxの解決策の問題は、次のとおりです」...この回答の問題とまったく同じです。想定される問題の解決策を提案する場合は、おそらくそれをテストする必要があります。:-)
paxdiablo 2015

私はこれで空の文字列を取得しました
アミナ・ヌライニ

-2

私は自分の仕事に関数を書いてくれました。PowerShellの2つのローマ数字関数を次に示します。

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.