如何只使用正则表达式匹配有效的罗马数字?

时间:2008-11-06 01:29:25

标签: regex roman-numerals

考虑my other problem,我决定甚至不能创建一个与罗马数字相匹配的正则表达式(更不用说将生成它们的无上下文语法)

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

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

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

我怎样才能在正则表达式中表达这一点? 如果它在正则表达式中根本无法表达,它是否可以在无上下文语法中表达出来?

17 个答案:

答案 0 :(得分:307)

尝试:

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

  

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

这是单位部分,处理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

答案 1 :(得分:19)

实际上,你的前提是有缺陷的。 990 IS “XM”,以及“CMXC”。

与三年级老师相比,罗马人更少关注“规则”。只要它加起来,就可以了。因此,“IIII”和4的“IV”一样好。而“IIM”对998来说非常酷。

(如果你在处理这个问题时遇到了麻烦......记住英语拼写直到1700年才正式化。在此之前,只要读者能够弄明白,这就足够了。)

答案 2 :(得分:12)

为避免匹配空字符串,您需要重复此模式四次,并依次用0替换每个1,并考虑V,{{1} }和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})) ^),您最好首先检查空行并且不要打扰它们。如果您使用的是word boundaries,那么您就没有问题,因为没有空字。 (至少正则表达式没有定义一个;不要开始哲学思考,我在这里务实!)


在我自己特定的(现实世界)案例中,我需要在单词结尾处使用匹配数字,而我找不到其他方法。我需要从我的纯文本文档中删除脚注编号,其中诸如“红海 cl 和大堡礁 cli ”之类的文本已转换为{{ 1}}。但我仍然遇到问题,例如$the Red Seacl and the Great Barrier Reefcli等有效字词被清除到Tahitifantastic

答案 3 :(得分:11)

只是为了保存它:

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

答案 4 :(得分: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})/;

答案 5 :(得分:6)

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})的唯一区别是因为我发现&#39; MMMM&#39;不是一个有效的罗马数字(也许老罗马人很可能没有考虑过这个庞大的数字,并且不同意我的观点)。如果你是一个不同意的老罗马人,请原谅我并使用{0,4}版本。

答案 6 :(得分:2)

我在Regular Expression in Python for Roman Numerals处回答这个问题
因为它被标记为该问题的完全重复。

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

可以将要查找的项目组合成一个单独的替代项目,然后
封装在捕获组中,该捕获组将与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)
 $

答案 7 :(得分:1)

正如杰里米和帕克斯在上面指出的...... “^ M {0,4}(CM | CD | d 13 C {0,3}?)(XC | XL?| L X {0,3})(IX | IV?| V I {0,3}) $'应该是你追求的解决方案......

应该附加的特定URL(恕我直言)是 http://thehazeltree.org/diveintopython/7.html

示例7.8是使用{n,m}

的缩写形式

答案 8 :(得分:1)

以下表达式对我有用,以验证罗马数字。

TASK [aci-fabric-onboarding : Add OBB address] *****************************************************************************************************************************************************
task path: /etc/ansible/Ansible-Aci-config/roles/aci-fabric-onboarding/tasks/apply-oob-config.yml:4
fatal: [25.96.131.30]: FAILED! => {
    "msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined\n\nThe error appears to be in '/etc/ansible/Ansible-Aci-config/roles/aci-fabric-onboarding/tasks/apply-oob-config.yml': line 4, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n# Adding OBB address\n- name: Add OBB address\n  ^ here\n"
}

在这里

  • ^M{0,4}(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$ 将匹配数千个
  • M{0,4}将匹配数百个
  • C[MD]|D?C{0,3}将匹配十位
  • X[CL]|L?X{0,3}将匹配单位

下面是一个可视化图表,可帮助您了解正在执行的操作,并先进行两个在线演示:

Debuggex Demo

Regex 101 Demo

Python代码:

I[XV]|V?I{0,3}

答案 9 :(得分:1)

我已经看到多个答案,这些答案不包含空字符串或使用先行方式解决此问题。我想添加一个新的答案,该答案涵盖空字符串并且不使用超前。正则表达式如下:

^(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 Demo

Regex 101 Demo

Regular expression visualization

答案 10 :(得分:0)

这里有一些非常惊人的答案,但没有一个适合我,因为我需要能够只匹配字符串中的有效罗马数字而不匹配空字符串,并且只匹配它们自己的数字(即不在一个单词内) .

让我向您介绍Reilly's Modern roman numerals strict expression

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

开箱即用,它与我需要的非常接近,但它只会匹配独立的罗马数字,当更改为匹配字符串时,它将在某些点匹配空字符串(单词以大写 V、M 等开头) .) 并且还会部分匹配无效的罗马数字,例如 MMLLVVDD、XXLLVVDD、MMMMDLVX、XVXDLMM 和 MMMCCMLXXV。

所以,经过一些修改后,我最终得到了这个:

(?<![MDCLXVI])(?=[MDCLXVI])M{0,3}(?:C[MD]|D?C{0,3})(?:X[CL]|L?X{0,3})(?:I[XV]|V?I{0,3})[^ ]\b

添加的 negative lookbehind 将确保它不会对无效罗马数字进行部分匹配,并将第一个 M 锁定为 3,因为这是 Roman numeral standard form 中的最高值。

截至目前,这是唯一通过我的 extensive test suit 测试的正则表达式,其中包括 1-3999 中所有可能的罗马数字、字符串中的罗马数字以及我提到的无效罗马数字

以下是来自 https://regex101.com/ 的屏幕截图: 4

答案 11 :(得分:0)

@paxdiablo为避免匹配空字符串而建议的正向后向和向前看对我来说似乎不起作用。

我通过改用提前修复了它:

foreach

注意:如果您在正则表达式的末尾添加一些内容(例如“ foobar”,那么显然您必须将(?!$)M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}) 替换为(?!$)(其中(?!f)是“ foobar”的第一个字符)。

答案 12 :(得分: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

答案 13 :(得分:0)

在我的情况下,我试图用文本内的一个单词查找并替换所有出现的罗马数字,因此我无法使用行的开头和结尾。因此,@ 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

答案 14 :(得分:0)

Steven Levithan在his post中使用此正则表达式,在“去除”值之前验证罗马数字:

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

答案 15 :(得分:-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])$

答案 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
    {
    }
}