184

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

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

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

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

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

4

17 回答 17

360

您可以为此使用以下正则表达式:

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

(另一种选择是预先检查长度是否不为零)。

于 2008-11-06T01:35:41.510 回答
27

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

与你的三年级老师相比,罗马人对“规则”的关注程度要低得多。只要加起来就OK了。因此,对于 4,“IIII”与“IV”一样好。而“IIM”对于 998 来说非常酷。

(如果您对此有困难……请记住,英语拼写直到 1700 年代才正式化。在那之前,只要读者能弄清楚,就足够了)。

于 2008-11-06T01:51:36.237 回答
17

只是为了保存在这里:

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

于 2016-04-12T14:33:52.537 回答
13

为避免匹配空字符串,您需要重复该模式四次,并依次将每个替换0为 a 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}))

在这种情况下(因为此模式使用^and $),您最好先检查空行,不要费心匹配它们。如果您使用单词边界,那么您没有问题,因为没有空词之类的东西。(至少正则表达式没有定义一个;不要开始哲学化,我在这里很务实!)


在我自己的特定(现实世界)案例中,我需要在词尾匹配数字,但我没有找到其他方法。我需要从纯文本文档中删除脚注编号,其中诸如“红海cl和大堡礁cli ”之类的文本已转换为the Red Seacl and the Great Barrier Reefcli. 但是我仍然对有效单词有问题,例如Tahitiandfantastic被擦洗成Tahitand fantasti

于 2012-05-04T01:07:40.427 回答
8

幸运的是,数字的范围被限制在 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})/;
于 2008-11-06T01:36:18.950 回答
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} 版本。

于 2014-10-31T07:11:17.950 回答
3

就我而言,我试图在文本中用一个单词查找和替换所有出现的罗马数字,所以我不能使用行的开头和结尾。所以@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
于 2018-12-22T12:45:40.980 回答
2

我见过多个不涵盖空字符串或使用前瞻来解决此问题的答案。而且我想添加一个新的答案,它确实涵盖了空字符串并且不使用前瞻。正则表达式如下:

^(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 如果需要。

下面是一个有助于理解它在做什么的可视化,前面有两个在线演示:

调试演示

正则表达式 101 演示

正则表达式可视化

于 2020-02-29T21:12:52.860 回答
1

正如杰里米和帕克斯在上面指出的那样...... '^M{0,4}(CM|CD|D?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} 的简写形式

于 2008-11-06T03:03:07.433 回答
1

我在这里回答这个问题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)
 $
于 2019-12-12T18:50:05.040 回答
1

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

^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}将匹配十
  • I[XV]|V?I{0,3}将匹配单位

下面是一个有助于理解它在做什么的可视化,前面有两个在线演示:

调试演示

正则表达式 101 演示

蟒蛇代码:

import re
regex = re.compile("^M{0,4}(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$")
matchArray = regex.match("MMMCMXCIX")
于 2020-09-07T09:28:29.713 回答
1

@paxdiablo 为了避免匹配空字符串而建议的积极的后视和前瞻对我来说似乎不起作用。

我已经通过使用前瞻来修复它:

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

注意:如果您在正则表达式末尾附加一些内容(例如,“foobar”,那么显然您必须替换(?!$)(?!f)f“foobar”的第一个字符在哪里)。

于 2020-09-23T19:09:25.957 回答
0

Steven Levithan 在他的帖子中使用了这个正则表达式,它在“deromanizing”值之前验证罗马数字:

/^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/
于 2014-06-22T19:26:33.570 回答
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

于 2020-02-29T03:36:24.040 回答
0

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

让我向您介绍Reilly 的现代罗马数字严格表达式

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

添加的负面后视将确保它不会对无效罗马数字进行部分匹配,并将第一个 M 锁定为 3,因为这是罗马数字标准形式中的最高值。

到目前为止,这是唯一一个通过了我超过 4000 次测试的正则表达式,其中包括从 1 到 3999 的所有可能的罗马数字、字符串中的罗马数字和我上面提到的无效罗马数字。

这是来自https://regex101.com/的截图: 4

于 2021-07-20T07:15:42.770 回答
-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])$
于 2011-03-16T14:12:41.907 回答
-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
    {
    }
}
于 2015-01-01T11:59:14.767 回答