Làm thế nào để bạn chỉ khớp các chữ số La Mã hợp lệ với một biểu thức chính quy?


165

Suy nghĩ về vấn đề khác của mình , tôi quyết định tôi thậm chí không thể tạo một biểu thức chính quy sẽ khớp với các chữ số La Mã (chứ đừng nói đến một ngữ pháp không ngữ cảnh sẽ tạo ra chúng)

Vấn đề chỉ phù hợp với các chữ số La Mã hợp lệ. Ví dụ: 990 không phải là "XM", đó là "CMXC"

Vấn đề của tôi khi tạo regex cho việc này là để cho phép hoặc không cho phép một số ký tự nhất định, tôi cần nhìn lại. Hãy lấy hàng ngàn và hàng trăm, ví dụ.

Tôi có thể cho phép M {0,2} C? M (để cho phép 900, 1000, 1900, 2000, 2900 và 3000). Tuy nhiên, nếu trận đấu diễn ra trên CM, tôi không thể cho phép các ký tự sau là C hoặc D (vì tôi đã ở mức 900).

Làm thế nào tôi có thể diễn đạt điều này trong một regex?
Nếu nó đơn giản là không thể diễn đạt được trong một biểu thức chính quy, thì nó có thể diễn đạt bằng ngữ pháp không ngữ cảnh không?

Câu trả lời:


328

Bạn có thể sử dụng regex sau đây cho việc này:

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

Phá vỡ nó, M{0,4}chỉ định phần ngàn và về cơ bản hạn chế nó ở giữa 04000. Nó tương đối đơn giản:

   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}

Tất nhiên, bạn có thể sử dụng một cái gì đó như M*để cho phép bất kỳ số nào (bao gồm số không) trong số hàng ngàn, nếu bạn muốn cho phép số lớn hơn.

Tiếp theo (CM|CD|D?C{0,3}), phức tạp hơn một chút, đây là phần trăm và bao gồm tất cả các khả năng:

  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

Thứ ba, (XC|XL|L?X{0,3})tuân theo các quy tắc tương tự như phần trước nhưng đối với vị trí hàng chục:

 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

Và cuối cùng, (IX|IV|V?I{0,3})là phần đơn vị, xử lý 0xuyên suốt 9và cũng tương tự như hai phần trước (chữ số La Mã, mặc dù có vẻ kỳ lạ, hãy tuân theo một số quy tắc logic khi bạn tìm ra chúng là gì):

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

Chỉ cần nhớ rằng regex đó cũng sẽ khớp với một chuỗi rỗng. Nếu bạn không muốn điều này (và công cụ regex của bạn đủ hiện đại), bạn có thể sử dụng cái nhìn tích cực và nhìn về phía trước:

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

(cách khác là chỉ kiểm tra xem độ dài không bằng 0 trước đó).


12
Không phải là M {0,3} sao?
chanh

3
giải pháp nào để tránh khớp chuỗi rỗng?
Facundo Casco

11
@Aashish: Khi người La Mã là một lực lượng được tính toán, MMMMlà cách chính xác. Các đại diện overbar xuất hiện rất lâu sau khi đế chế cốt lõi sụp đổ thành từng mảnh.
paxdiablo

2
@paxdiablo đây là cách tôi tìm thấy mmmcm thất bại. Chuỗi 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)) -> điều này đánh giá sai thành MMMCM / MMMM trong java.
Amit

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

Trên thực tế, tiền đề của bạn là thiếu sót. 990 IS "XM", cũng như "CMXC".

Người La Mã ít quan tâm đến "quy tắc" hơn so với giáo viên lớp ba của bạn. Miễn là nó được thêm vào, nó là OK. Do đó "IIII" cũng tốt như "IV" cho 4. Và "IIM" hoàn toàn tuyệt vời cho 998.

(Nếu bạn gặp khó khăn trong việc xử lý điều đó ... Hãy nhớ cách viết tiếng Anh không được chính thức hóa cho đến những năm 1700. Cho đến lúc đó, miễn là người đọc có thể hiểu được, nó đã đủ tốt rồi).


8
Chắc chắn, đó là mát mẻ. Nhưng cú pháp "giáo viên lớp ba nghiêm khắc" của tôi cần tạo ra một vấn đề regex thú vị hơn nhiều, theo ý kiến ​​của tôi ...
Daniel Magliola

5
James tốt, một người nên là một tác giả nghiêm khắc nhưng là một người đọc tha thứ.
Corin


13

Chỉ để lưu nó ở đây:

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

Phù hợp với tất cả các chữ số La Mã. Không quan tâm đến các chuỗi trống (yêu cầu ít nhất một chữ số La Mã). Nên hoạt động trong PCRE, Perl, Python và Ruby.

Bản demo Ruby trực tuyến: http://rubular.com/r/KLPR1zq3Hj

Chuyển đổi trực tuyến: http://www.onlineconversion.com/roman_numemony_advified.htmlm


2
Tôi không biết tại sao, nhưng câu trả lời chính không phù hợp với tôi trong danh sách tự động chuyển trong MemQ. Tuy nhiên, giải pháp này thực hiện - không bao gồm các ký hiệu bắt đầu / kết thúc chuỗi.
orlando2bjr

1
@ orlando2bjr rất vui khi được giúp đỡ. Vâng, trong trường hợp này, tôi đã tự khớp một số, không có môi trường xung quanh. Nếu bạn tìm nó trong một văn bản, chắc chắn bạn sẽ cần xóa ^ $. Chúc mừng!
smileart

12

Để tránh khớp chuỗi trống, bạn cần lặp lại mẫu bốn lần và thay thế từng 0chuỗi 1bằng lần lượt và tính đến 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}))

Trong trường hợp này (vì mẫu này sử dụng ^$) trước tiên bạn nên kiểm tra các dòng trống và không bận tâm khớp chúng. Nếu bạn đang sử dụng ranh giới từ thì bạn không gặp vấn đề gì vì không có từ nào là từ trống. (Ít nhất regex không định nghĩa một; đừng bắt đầu triết lý, tôi đang thực dụng ở đây!)


Trong trường hợp cụ thể (thế giới thực) của riêng tôi, tôi cần khớp các chữ số ở cuối từ và tôi không tìm thấy cách nào khác xung quanh nó. Tôi cần xóa sạch các số chú thích từ tài liệu văn bản đơn giản của mình, trong đó văn bản như " cl Biển Đỏ và cli Rạn san hô Great Barrier " đã được chuyển đổi thành the Red Seacl and the Great Barrier Reefcli. Nhưng tôi vẫn có vấn đề với các từ hợp lệ như Tahitifantasticđang cọ vào Tahitfantasti.


Tôi có một vấn đề tương tự (!): Để thực hiện "cắt xén trái" số La Mã còn lại / còn lại của danh sách vật phẩm (HTML OL loại I hoặc i). Vì vậy, khi có được còn lại, tôi cần phải sạch (giống như một chức năng cắt) với regex của bạn vào đầu (bên trái) của mặt hàng văn ... Nhưng đơn giản hơn: Các sản phẩm không bao giờ sử dụng Mhoặc Choặc L, vì vậy, bạn có này loại regex đơn giản hóa?
Peter Krauss

... ok, ở đây có vẻ ổn (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Peter Krauss

1
bạn không cần lặp lại mẫu, để từ chối các chuỗi trống. Bạn có thể sử dụng một xác nhận tìm kiếm
jfs

7

May mắn thay, phạm vi số được giới hạn ở 1..3999 hoặc khoảng đó. Do đó, bạn có thể xây dựng bữa ăn regex.

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

Mỗi phần đó sẽ đối phó với những điều mơ hồ của ký hiệu La Mã. Ví dụ: sử dụng ký hiệu Perl:

<opt-hundreds-part> = m/(CM|DC{0,3}|CD|C{1,3})?/;

Lặp lại và lắp ráp.

Đã thêm : Có <opt-hundreds-part>thể được nén thêm:

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

Vì mệnh đề 'D? C {0,3}' không khớp với gì, nên không cần có dấu hỏi. Và, rất có thể, dấu ngoặc đơn phải là loại không bắt - trong Perl:

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

Tất nhiên, tất cả cũng không nên phân biệt chữ hoa chữ thường.

Bạn cũng có thể mở rộng điều này để đối phó với các tùy chọn được đề cập bởi James Curran (để cho phép XM hoặc IM cho 990 hoặc 999 và CCCC cho 400, v.v.).

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

Bắt đầu với thousands hundreds tens units, thật dễ dàng để tạo ra một FSM tính toán và xác nhận các chữ số La Mã
jfs

Bạn có ý gì bởi May mắn thay, phạm vi số được giới hạn ở 1..3999 hoặc khoảng đó ? Ai giới hạn nó?
SexyBeast

@SexyBeast: Không có bất kỳ ký hiệu La Mã tiêu chuẩn nào cho 5.000, chứ đừng nói đến những con số lớn hơn, vì vậy các quy tắc hoạt động đến lúc đó sẽ ngừng hoạt động.
Jonathan Leffler

Không chắc chắn tại sao bạn tin điều đó, nhưng chữ số La Mã có thể đại diện cho hàng triệu. vi.wikipedia.org/wiki/Roman_numemony#Large_numbers
AmbroseChapel

@AmbroseChapel: Như tôi đã nói, không có ký hiệu chuẩn (đơn) nào cho 5.000, chứ đừng nói đến số lớn hơn. Bạn phải sử dụng một trong số một số hệ thống khác nhau như được nêu trong bài viết Wikipedia mà bạn liên kết đến và bạn phải đối mặt với các vấn đề về chỉnh hình cho hệ thống với các thanh ngang, gạch chân hoặc đảo ngược C, v.v. Và bạn sẽ phải giải thích cho bất cứ ai biết hệ thống bạn đang sử dụng và ý nghĩa của nó; nói chung, mọi người sẽ không nhận ra các chữ số La Mã ngoài M. Bạn có thể chọn nghĩ khác; đó là đặc quyền của bạn, giống như đó là đặc quyền của tôi khi đứng trước những bình luận trước đây của tôi.
Jonathan Leffler

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'

Đối với những người thực sự muốn hiểu logic, xin vui lòng xem giải thích từng bước trên 3 trang trên diveintopython .

Sự khác biệt duy nhất so với giải pháp ban đầu (có M{0,4}) là vì tôi thấy rằng 'MMMM' không phải là một chữ số La Mã hợp lệ (cũng là người La Mã cũ có lẽ đã không nghĩ về con số khổng lồ đó và sẽ không đồng ý với tôi). Nếu bạn là một trong những người La Mã cũ không đồng ý, xin vui lòng tha thứ cho tôi và sử dụng phiên bản {0,4}.


1
regex trong câu trả lời cho phép các chữ số trống. Nếu bạn không muốn nó; bạn có thể sử dụng một xác nhận nhìn , để từ chối các chuỗi trống (nó cũng bỏ qua trường hợp của các chữ cái).
jfs

2

Tôi đang trả lời câu hỏi này Biểu thức chính quy trong Python cho số La Mã ở đây
vì nó được đánh dấu là một bản sao chính xác của câu hỏi này.

Nó có thể giống nhau về tên, nhưng đây là một câu hỏi / vấn đề regex cụ
thể có thể được nhìn thấy bởi câu trả lời này cho câu hỏi đó.

Các mục đang được tìm kiếm có thể được kết hợp thành một luân phiên duy nhất và sau đó
được đặt trong một nhóm chụp sẽ được đưa vào một danh sách với hàm findall ()
.
Nó được thực hiện như thế này:

>>> 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']

Các sửa đổi regex cho yếu tố và chỉ nắm bắt các chữ số là:

 (?m)
 \s 
 (                     # (1 start)
      i{1,3} 
      v* 
   |  v
 )                     # (1 end)
 $

1

Như Jeremy và Pax đã chỉ ra ở trên ... '^ M {0,4} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0,3}) $ 'sẽ là giải pháp bạn theo sau ...

URL cụ thể cần được đính kèm (IMHO) là http://thehazeltree.org/diveintopython/7.html

Ví dụ 7.8 là dạng ngắn sử dụng {n, m}


1

Trong trường hợp của tôi, tôi đã cố gắng tìm và thay thế tất cả các lần xuất hiện của số La Mã bằng một từ bên trong văn bản, vì vậy tôi không thể sử dụng bắt đầu và kết thúc dòng. Vì vậy, giải pháp @paxdiablo đã tìm thấy nhiều kết quả trùng khớp có độ dài bằng không. Tôi đã kết thúc với biểu thức sau đây:

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

Mã Python cuối cùng của tôi là như thế này:

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)

Đầu ra:

RULES OF LIFE: ROMAN. STAY CURIOUS; ROMAN. NEVER STOP LEARNING

0

Steven Levithan sử dụng biểu thức chính này trong bài đăng của mình để xác thực các chữ số La Mã trước khi "làm mất giá trị" giá trị:

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

0

Tôi đã thấy nhiều câu trả lời không bao gồm các chuỗi trống hoặc sử dụng bảng tìm kiếm để giải quyết vấn đề này. Và tôi muốn thêm một câu trả lời mới bao gồm các chuỗi trống và không sử dụng lookahead. Regex là một trong những điều sau đây:

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

Tôi đang cho phép vô hạn M, M+nhưng tất nhiên ai đó có thể thay đổi thành M{1,4}chỉ cho phép 1 hoặc 4 nếu muốn.

Dưới đây là một hình ảnh trực quan giúp hiểu những gì nó đang làm, trước hai bản demo trực tuyến:

Trình diễn gỡ lỗi

Bản giới thiệu Regex 101

Hình dung biểu thức thường xuyên


0

Điều này hoạt động trong các công cụ regex Java và PCRE và bây giờ sẽ hoạt động trong JavaScript mới nhất nhưng có thể không hoạt động trong tất cả các ngữ cảnh.

(?<![A-Z])(M*(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3}))(?![A-Z])

Phần đầu tiên là cái nhìn tiêu cực tàn bạo. Nhưng, với mục đích hợp lý, nó là dễ hiểu nhất. Về cơ bản, người đầu tiên (?<!)nói không khớp giữa ([MATCH])nếu có chữ cái đến trước giữa ([MATCH])và cuối cùng (?!)là nói không khớp giữa ([MATCH])nếu có chữ cái đến sau.

Giữa ([MATCH])chỉ là regex được sử dụng phổ biến nhất để phù hợp với chuỗi số La Mã. Nhưng bây giờ, bạn không muốn khớp với điều đó nếu có bất kỳ chữ cái nào xung quanh nó.

Xem cho chính mình. https://regexr.com/4vce5


-1

Vấn đề của giải pháp từ Jeremy và Pax là, nó cũng không khớp với "không có gì".

Regex sau đây mong đợi ít nhất một chữ số La Mã:

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

6
cái đó sẽ không hoạt động (trừ khi bạn đang sử dụng một triển khai regex rất kỳ lạ) - phần bên trái của chuỗi |có thể khớp với một chuỗi trống và tất cả các chữ số La Mã hợp lệ, vì vậy phía bên phải là hoàn toàn dư thừa. và vâng, nó vẫn khớp với một chuỗi rỗng.
DirtY iCE

"Vấn đề của giải pháp từ Jeremy và Pax là" ... chính xác giống như vấn đề mà câu trả lời này có. Nếu bạn định đề xuất một giải pháp cho một vấn đề giả định, có lẽ bạn nên kiểm tra nó. :-)
paxdiablo

Tôi nhận được chuỗi rỗng với điều này
Aminah Nuraini

-2

Tôi sẽ viết các chức năng cho công việc của tôi cho tôi. Đây là hai hàm số La Mã trong 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
    {
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.