你如何才能与正则表达式匹配有效的罗马数字?

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

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

我为这个正则表达式的问题是,为了允许或不允许某些字符,我需要回头看看。 例如,我们需要数千和数百个。

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

我怎样才能表达这个正则表达式?
如果它在一个正则表达式中不能表达,那么它是否可以用上下文无关语法来表示?

尝试:

^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 

其实,你的前提是有缺陷的。 990 “XM”,还有“CMXC”。

罗马人比你的三年级老师更少关心“规则”。 只要加起来就没问题 因此,“IIII”和4的“IV”一样好。998年的“IIM”完全酷了。

(如果你在处理这个问题时遇到了麻烦……请记住,直到十八世纪,英语拼写还没有正式化,在此之前,只要读者能够弄清楚,那就足够了)。

为了避免匹配空字符串,你需要重复这个模式四次,并依次用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})) 

在这种情况下(因为这个模式使用^$ ),你最好先检查空行,不要打扰匹配它们。 如果你使用的是单词边界,那么你就没有问题,因为没有空单词这样的事情。 (至少正则表达式并没有定义一个;不开始哲学,我在这里务实!)


在我自己特定的(现实世界)情况下,我需要在单词结尾处的匹配数字,而且我没有发现任何其他的方式。 我需要清除我的纯文本文件中的脚注数字,其中“红海cl和大堡礁cli ”等文本已被转换为the Red Seacl and the Great Barrier Reefcli 。 但是我仍然遇到像Tahiti这样有效的Tahit ,而且fantastic被洗进了Tahitfantasti

幸运的是,数字范围限制在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})/; 

只是为了保存在这里:

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

 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}版本。

正如杰里米(Jeremy)和帕克斯(Pax)在上面指出的那样:(CM | CD | D?C {0,3})(XC | XL | L?X {0,3}) | V?I {0,3})$'应该是你之后的解决方案…

应该附上的具体URL(恕我直言)是http://thehazeltree.org/diveintopython/7.html

例7.8是使用{n,m}

杰里米和帕克斯解决的问题是,它也匹配“无”。

以下正则表达式至少需要一个罗马数字:

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

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

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

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