如何仅将有效的罗马数字与正则表达式匹配?


165

考虑到我的另一个问题,我决定甚至不能创建一个匹配罗马数字的正则表达式(更不用说会生成它们的无上下文语法)

问题是仅匹配有效的罗马数字。例如,990不是“ XM”,而是“ CMXC”

我为此制作正则表达式的问题是,为了允许或不允许某些字符,我需要回顾一下。让我们以成千上万个为例。

我可以允许M {0,2} C?M(允许900、1000、1900、2000、2900和3000)。但是,如果匹配是在CM上,则我不允许后面的字符为C或D(因为我已经在900岁了)。

我如何在正则表达式中表达这一点?
如果在正则表达式中根本无法表达它,那么在上下文无关的语法中可以表达它吗?

Answers:


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}吗?
柠檬2010年

3
任何解决方案,以避免匹配空字符串?
Facundo Casco

11
@Aashish:当罗马人是一支不可忽视的力量时,这MMMM是正确的方法。在核心帝国瓦解之后很久就出现了这种夸张的表象。
paxdiablo

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。
2014年

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

与您的三年级老师相比,罗马人对“规则”的关注要少得多。只要加起来就可以了。因此,“ IIII”与“ IV”在4方面一样好。“ IIM”在998中完全很酷。

(如果您不能解决这个问题,请记住英语拼写直到1700年代才正式化。在那之前,只要读者能弄清楚,它就足够了。)


8
当然可以,很酷。但是我认为我的“严格的三年级老师”语法需求使正则表达式问题更加有趣……
Daniel Magliola,

5
好的观点,詹姆斯应该是一位严格的作者,但要宽容的读者。
科林2012年

@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中使用。

在线Ruby演示: http //rubular.com/r/KLPR1zq3Hj

在线转换:http : //www.onlineconversion.com/roman_numerals_advanced.htm


2
我不知道为什么,但是在MemoQ中自动翻译列表时,主要答案对我不起作用。但是,此解决方案可以-尽管排除字符串开始/结束符号。
orlando2bjr

1
@ orlando2bjr很高兴为您提供帮助。是的,在这种情况下,我是在没有环境的情况下自行匹配一个数字。如果您在文本中查找它,请确保您需要删除^ $。干杯!
smileart '17

12

为了避免空字符串匹配,你需要重复模式四次,每次更换0一个1又和账户VLD

(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被擦洗到Tahit和中fantasti


我有类似的问题(!):对项目列表(类型I或i的HTML OL)的剩余/残余罗马数字进行“左修剪”。因此,当有剩余时,我需要在项目文本的开头(左侧)使用正则表达式进行清理(如修整功能)...但是更简单:项目从不使用MCL,所以您是否拥有此功能简化的正则表达式?
Peter Krauss 2014年

...好的,这里看起来还不错(!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
彼得·克劳斯

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提到的选项(允许XM或IM用于990或999,CCCC用于400等)。

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

从开始thousands hundreds tens units,很容易创建一个用于计算和验证给定罗马数字的FSM
jfs

你是什么意思幸运的是,数字的范围仅限于1..3999点左右?谁限制了它?
SexyBeast

@SexyBeast:5,000并没有标准的罗马符号,更不用说更大的数字了,因此可以正常工作的规则将停止工作。
乔纳森·莱夫勒

不知道为什么会相信,但是罗马数字可以表示成百万的数字。en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel

@AmbroseChapel:正如我所说的,5,000没有任何(单一)标准符号,更不用说更大的数字了。您必须使用所链接到的Wikipedia文章中概述的多种系统中的一种,并且在正字法,负数法或倒C字法下,系统正字法会遇到问题。您将必须向任何人解释什么您正在使用的系统及其含义;人们通常不会认识到M以外的罗马数字。那是您的特权,就像我保留我以前的评论一样。
乔纳森·莱夫勒

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'

对于真正想了解逻辑的人,请看一下关于deepintopython的 3页上的逐步说明

与原始解决方案(具有M{0,4})的唯一区别是,因为我发现'MMMM'不是有效的罗马数字(同样,旧罗马人很可能没有考虑过这个巨大的数字,因此会与我不同意)。如果您是不同意的古罗马人之一,请原谅我并使用{0,4}版本。


1
答案中的正则表达式允许使用空数字。如果您不想要它;您可以使用前瞻性断言拒绝空字符串(它也忽略字母的大小写)。
jfs

2

这里回答这个问题,
因为它被标记为该问题的完全重复。

它的名称可能相似,但这是一个特定的正则表达式问题/问题
,从对该问题的答案可以看出。

可以将要查找的项组合成一个单独的替代项,然后将其
包含在捕获组中,该捕获组将使用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解决方案发现了许多零长度匹配项。我最终得到以下表达式:

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

我允许使用infinite MM+但如果有人愿意,当然可以更改为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
那将是行不通的(除非您使用的是非常怪异的正则表达式实现)-的左侧部分|可以匹配一个空字符串和所有有效的罗马数字,因此右侧是完全多余的。是的,它仍然匹配一个空字符串。
DirtY iCE 2011年

“来自Jeremy和Pax的解决方案的问题是……”与该答案存在的问题完全相同。如果您打算为一个假定的问题提出解决方案,则可能应该对其进行测试。:-)
paxdiablo

我得到了这个空字符串
Aminah Nuraini '16

-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
    {
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.