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

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

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

为此创建正则表达式的问题在于,为了允许或不允许某些字符,我需要回过头来看。 举个例子,成千上万个。

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

如何在正则表达式中表达这一点?
如果它在正则表达式中是不可表达的,那么它在上下文无关文法中是可表达的吗?

96455 次浏览

可以使用以下正则表达式:

^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

第三,(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

请记住,正则表达式也将匹配一个空字符串。如果您不希望这样(并且您的正则表达式引擎足够现代) ,可以使用积极的 look-behind 和 look-ahead:

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

(另一种方法是事先检查长度是否为零)。

幸运的是,数字的范围限制在1.3999左右。

<opt-thousands-part><opt-hundreds-part><opt-tens-part><opt-units-part>

这些部分都将处理罗马符号的变化:

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

从死亡之后?C {0,3}’子句不能匹配任何内容,因此没有必要使用问号。而且,最有可能的是,括号应该是非捕获类型-in 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})/;

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

罗马人远不如你三年级的老师关心“规则”。只要合情合理,就没问题。因此“ III”对于4来说和“ IV”一样好。而且“ IIM”对998来说太酷了。

(如果你在处理这个问题上有困难... ... 请记住,英语的拼写直到18世纪才正式确定下来。在那之前,只要读者能够理解它,它就足够好了)。

Jeremy 和 Pax 的解决方案的问题是,它也匹配“无”。

下列正则表达式期望至少有一个罗马数字:

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

为了避免匹配空字符串,你需要重复这个模式四次,依次用一个 1替换每个 0,并考虑到 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}))

在这种情况下(因为这个模式使用 ^$) ,最好先检查空行,不要费心匹配它们。如果您使用的是 单词界限,那么您就没有问题,因为根本就没有所谓的空词这回事。(至少 regex 没有定义一个; 不要开始哲学化,我在这里是务实的!)


在我自己的特殊情况下(真实世界) ,我需要匹配数字在词尾,我找不到其他方法绕过它。我需要擦掉纯文本文档中的脚注号码,其中的文本如“红海 克莱尔和大堡礁 Cli”已转换为 the Red Seacl and the Great Barrier Reefcli。但是我仍然有问题,像 Tahitifantastic这样的有效词汇被擦洗成 Tahitfantasti

Steven Levithan 在 他的职位中使用了这个 regex,它在“反罗马化”值之前验证罗马数字:

/^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/
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}版本。

我会为我的工作写函数。 下面是 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
{
}
}

省省吧:

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

在我的例子中,我试图在文本中找到并用一个单词替换所有出现的罗马数字,所以我不能使用行的开始和结束。因此,@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

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

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

寻找的项目可以合并成一个单一的交替,然后
封装在一个捕获组中,该捕获组将用 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)
$

这可以在 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])

中间的 ([MATCH])是最常用的匹配罗马数字序列的正则表达式。但是现在,如果周围有任何字母,你就不会想要匹配它了。

你自己看吧。 Https://regexr.com/4vce5

我已经看到了许多答案,它们并没有涵盖空字符串,或者使用预先知道的方法来解决这个问题。我想添加一个新的答案,它确实覆盖了空字符串,并且不使用向前看。正则表达式如下:

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

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

调试演示

正则表达式101演示

Regular expression visualization

下面的表达式对我来说很有用,可以验证罗马数字。

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

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

调试演示

正则表达式101演示

Python 代码:

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

@ 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”的第一个字符)。

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

让我向你们介绍 莱利现代罗马数字的严格表达:

^(?=[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 和 MMmcMLXXV 的部分匹配。

所以,经过一些修改,我得出了这样的结论:

(?<![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