Düzenli bir ifadeyle yalnızca geçerli romen rakamlarını nasıl eşleştiriyorsunuz?


165

Diğer sorunumu düşünerek , romen rakamlarıyla eşleşen normal bir ifade bile oluşturamamaya karar verdim.

Sorun sadece geçerli romen rakamlarıyla eşleşiyor. Örneğin, 990 "XM" DEĞİL, "CMXC"

Bunun için normal ifadeyi yaparken benim sorunum, belirli karakterlere izin vermek veya izin vermemek için, geriye bakmam gerekiyor. Örneğin binlerce ve yüzlerce örnek alalım.

M {0,2} C? M'ye izin verebilirim (900, 1000, 1900, 2000, 2900 ve 3000'e izin vermek için). Ancak, maç CM'de ise, aşağıdaki karakterlerin C veya D olmasına izin veremem (çünkü zaten 900'dayım).

Bunu normal ifadeyle nasıl ifade edebilirim?
Bir normal ifade ile ifade edilemiyorsa, bağlamdan bağımsız bir gramerde ifade edilebilir mi?

Yanıtlar:


328

Bunun için aşağıdaki normal ifadeyi kullanabilirsiniz:

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

Aşağı Breaking, M{0,4}binlerce bölümünü belirtir ve temelde arasında onu alıkoyar 0ve 4000. Nispeten basit:

   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}

Elbette, daha büyük sayılara izin vermek istiyorsanız, herhangi bir sayıya (sıfır dahil) binlerce M*izin vermek gibi bir şey kullanabilirsiniz .

Sonraki (CM|CD|D?C{0,3}), biraz daha karmaşık, bu yüzlerce bölüm içindir ve tüm olasılıkları kapsar:

  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

Üçüncüsü, (XC|XL|L?X{0,3})onlarca yer için önceki bölümle aynı kuralları izler:

 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

Ve son olarak, (IX|IV|V?I{0,3})taşıma, birimler bölümdür 0içinden 9ve önceki iki bölümde (ne olduklarını anlamaya kez Romen rakamları, onların görünüşteki garabeti rağmen bazı mantıksal kuralları takip edin) da benzer:

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

Bu normal ifadenin de boş bir dizeyle eşleşeceğini unutmayın. Bunu istemiyorsanız (ve regex motorunuz yeterince modernse), olumlu bakış ve ileriye bakabilirsiniz:

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

(diğer alternatif, uzunluğun önceden sıfır olmadığını kontrol etmektir).


12
M {0,3} olmamalı mı?
limon

3
boş dize eşleşmesini önlemek için herhangi bir çözüm?
Facundo Casco

11
@Aashish: Romalılar hesaba katılması gereken bir güç olduğunda MMMM, doğru yoldu. Üst çubuk temsil, çekirdek imparatorluğun parçalanmasından çok sonra geldi.
paxdiablo

2
@paxdiablo mmmcm başarısız buldum. Dize 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)) -> bu, java'daki MMMCM / MMMM için false olarak değerlendirilir.
amIT

2
/^M{0,3}(?:C[MD]|D?C{0,3})(?:X[CL]|L?X{0,3})(?:I[XV]|V?I{0,3})$/i
19'da Crissov

23

Aslında, öncülünüz kusurludur. 990 IS "XM", hem de "CMXC".

Romalılar “kurallar” konusunda üçüncü sınıf öğretmeninizden çok daha az endişeliydiler. Eklendiği sürece, Tamam oldu. Bu nedenle "IIII" 4 için "IV" kadar iyiydi ve "IIM" 998 için tamamen harikaydı.

(Bununla başa çıkmakta zorluk çekiyorsanız ... 1700'lere kadar İngilizce yazımların resmileşmediğini hatırlayın. O zamana kadar, okuyucu bunu anlayabildiği sürece, yeterince iyiydi).


8
Tabii, bu harika. Ama benim "sıkı üçüncü sınıf öğretmeni" sözdizimi ihtiyacım, bence çok daha ilginç bir regex sorunu yapar ...
Daniel Magliola

5
İyi bir nokta James, bir kişi katı bir yazar ama affedici bir okuyucu olmalı.
Corin


13

Sadece buraya kaydetmek için:

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

Tüm Romen rakamlarıyla eşleşir. Boş dizeleri umursamaz (en az bir Romen rakamı harfi gerektirir). PCRE, Perl, Python ve Ruby'de çalışmalı.

Çevrimiçi Ruby demosu: http://rubular.com/r/KLPR1zq3Hj

Çevrimiçi Dönüşüm: http://www.onlineconversion.com/roman_numerals_advanced.htm


2
Neden bilmiyorum, ama ana cevap MemoQ otomatik çeviri listelerinde benim için çalışmadı. Ancak, bu çözüm - dize başlangıç ​​/ bitiş sembolleri hariç.
orlando2bjr

1
@ orlando2bjr yardımcı olmaktan mutluluk duyar. Evet, bu durumda çevremiz bir numarayı tek başımaydım. Bir metinde ararsanız, ^ $ 'ı kaldırmanız gerekir. Şerefe!
smileart

12

Boş dize eşleşen kaçınmak için size düzeninin dört kez tekrarlayın ve her değiştirmeniz gerekir 0bir ile 1için sırayla, ve hesap V, Lve 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}))

Bu durumda (bu desen ^ve kullandığından $) önce boş satırları kontrol etmekten daha iyi olur ve bunları eşleştirmeye zahmet etmeyin. Kelime sınırları kullanıyorsanız , boş bir kelime diye bir şey olmadığı için sorun yaşamazsınız. (En azından normal ifade birini tanımlamaz; felsefe yapmaya başlama, burada pragmatik oluyorum!)


Kendi özel (gerçek dünya) durumumda, kelime sonlarında eşleme rakamlarına ihtiyacım vardı ve bunun etrafında başka bir yol bulamadım. "Red Sea cl ve Great Barrier Reef cli " gibi metnin dönüştürüldüğü düz metin belgemdeki dipnot numaralarını temizlemem gerekiyordu the Red Seacl and the Great Barrier Reefcli. Ama hala gibi geçerli sözlerle sorunları vardı Tahitive fantasticiçine temizlendi edilir Tahitve fantasti.


Benzer bir sorun (!) Var: bir öğe listesinin (I veya i HTML OL) kalan / kalan roman sayısı "sol trim" yapmak için. Öyleyse, kalan varsa, öğe metninin başında (solda) normal ifadenizle (kırpma işlevi gibi) temizlemem gerekiyor ... Ama daha basit: öğeler asla kullanmaz Mveya Cveya L, basitleştirilmiş normal regex?
Peter Krauss

... tamam, işte öyle görünüyor (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Peter Krauss

1
boş dizeleri reddetmek için deseni tekrarlamanız gerekmez. Sen olabilir Bir lookahead iddiayı kullanmak
jfs

7

Neyse ki, sayı aralığı 1..3999 veya bununla sınırlıdır. Bu nedenle, normal regex parça yemek oluşturabilirsiniz.

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

Bu bölümlerin her biri Roma notalarının kaprisleriyle ilgilenecek. Örneğin, Perl gösterimini kullanarak:

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

Tekrarlayın ve monte edin.

Eklendi : Daha <opt-hundreds-part>fazla sıkıştırılabilir:

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

'D? C {0,3}' cümlesi hiçbir şeyle eşleşemediğinden, soru işaretine gerek yoktur. Ve büyük olasılıkla, parantezler Perl'de yakalamayan tür olmalıdır:

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

Tabii ki, hepsi de büyük / küçük harfe duyarsız olmalıdır.

Bunu James Curran tarafından belirtilen seçeneklerle başa çıkmak için de genişletebilirsiniz (990 veya 999 için XM veya IM'ye ve 400 için CCCC'ye izin vermek vb.).

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


Eğer ne demek istiyorsunuz Neyse ki, sayı aralığı 1..3999 veya oralarda sınırlıdır ? Kim sınırladı?
SexyBeast

@SexyBeast: Büyük rakamlar hariç, 5.000 için standart bir Roma notasyonu yoktur, bu yüzden işe yarayan düzenlilikler çalışmayı durdurur.
Jonathan Leffler

Neden buna inandığınızdan emin değilim, ancak Romen rakamları milyonları rakamları temsil edebilir. en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel

@AmbroseChapel: Daha önce de belirttiğim gibi, daha büyük sayılar olsun, 5.000 için (tek) standart gösterim yok. Bağlandığınız Wikipedia makalesinde ana hatlarıyla belirtildiği gibi bir dizi ıraksak sistemden birini kullanmanız ve sisteme ait overbarlar, alt çubuklar veya ters C vb. İle ortografi ile ilgili sorunlarla karşılaşmanız gerekir. kullandığınız sistem ve bunun anlamı; insanlar genel olarak M'nin ötesindeki Roma rakamlarını tanımayacaklardır. Aksi düşünmeyi tercih edebilirsiniz; bu sizin ayrıcalığınız, tıpkı önceki yorumlarımda durmak ayrıcalığım gibi.
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'

Gerçekten mantığı anlamak isteyenler için, lütfen diveintopython'daki 3 sayfada adım adım açıklamaya bir göz atın .

Orijinal çözümden (ki M{0,4}) olan tek fark, 'MMMM'nin geçerli bir Roma rakamı olmadığını (eski Romalıların büyük olasılıkla bu büyük sayıyı düşünmedikleri ve benimle aynı fikirde olmayacakları) bulmalarıydı. Eski Romalılara katılmıyorsanız, lütfen beni affedin ve {0,4} sürümünü kullanın.


1
cevaptaki normal ifade boş sayılara izin verir. Eğer istemiyorsanız; Eğer olabilir bir ileri yönlü iddiayı kullanmak (aynı zamanda küçük harf yok sayar) boş dizeleri reddetme.
jfs

2

Bu soruyu cevaplıyorum , Romen Rakamları için Python'da Düzenli İfade burada
çünkü bu sorunun tam bir kopyası olarak işaretlendi.

Adında benzer olabilir, ancak bu,
bu sorunun cevabında görülebileceği gibi, normal bir regex sorusu / sorunudur .

Aranan öğeler tek bir değişimle birleştirilebilir ve daha sonra
findall ()
işleviyle listeye alınacak bir yakalama grubunun içine yerleştirilebilir .
Bu şekilde yapılır:

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

Sadece sayıları hesaba katmak ve yakalamak için normal ifade değişiklikleri şunlardır:

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


1

Benim durumumda, roma sayılarının tüm oluşumlarını metnin içindeki bir kelimeyle bulmaya ve değiştirmeye çalışıyordum, bu yüzden satırların başlangıcını ve sonunu kullanamadım. Böylece @paxdiablo çözümü sıfır uzunluklu birçok eşleşme buldu. Sonunda şu ifadeyi aldım:

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

Son Python kodum şöyleydi:

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)

Çıktı:

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

0

Steven Levithan bu regex kullanır görevinden "deromanizing" değerine öncesinde romen rakamları doğrular:

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

0

Boş dizeleri içermeyen veya bunu çözmek için ileriye dönük kullanan birden fazla cevap gördüm. Ve boş dizeleri kapsayan ve ileriye dönük kullanmayan yeni bir cevap eklemek istiyorum. Normal ifade aşağıdaki gibidir:

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

Sonsuzluğa izin veriyorum M, M+ancak tabii ki birileri M{1,4}istenirse sadece 1 veya 4'e izin verecek şekilde değişebilir .

Aşağıda, iki çevrimiçi demodan önce ne yaptığını anlamaya yardımcı olan bir görselleştirme bulunmaktadır:

Debuggex Demosu

Regex 101 Demosu

Düzenli ifade görselleştirme


0

Bu, Java ve PCRE normal ifade motorlarında çalışır ve şimdi en son JavaScript'te çalışmalıdır, ancak tüm bağlamlarda çalışmayabilir.

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

İlk bölüm iğrenç olumsuz bakış. Ancak, mantıksal amaçlar için anlaşılması en kolay olanıdır. Temel olarak, (?<!)ortadan ([MATCH])önce gelen harfler varsa orta ile eşleşmeyeceğini ([MATCH])ve sondan sonra gelen harfler varsa (?!)orta ile eşleşmediğini ([MATCH])söyler.

Orta ([MATCH]), Romen Rakamları dizisini eşleştirmek için sadece en yaygın kullanılan regex'tir. Ama şimdi, etrafında herhangi bir harf varsa bunu eşleştirmek istemezsiniz.

Kendin için gör. https://regexr.com/4vce5


-1

Jeremy ve Pax'ın çözümü, "hiçbir şey" ile de eşleşmemesidir.

Aşağıdaki normal ifade en az bir romen rakamı beklemektedir:

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

6
(çok garip bir normal ifade uygulaması kullanmadığınız sürece) - |kutunun sol kısmı boş bir dizeyle ve geçerli tüm romen rakamlarıyla eşleşebilir, bu nedenle sağ taraf tamamen gereksizdir. ve evet, hala boş bir dizeyle eşleşiyor.
DirtY iCE

"Jeremy ve Pax'ın çözüm sorunu" ... bu sorunun cevabı ile tamamen aynı. Sözde bir soruna çözüm önerecekseniz, muhtemelen test etmelisiniz. :-)
paxdiablo

Bu ile boş bir dize var
Aminah Nuraini

-2

İşime benim için işlevler yazardım. İşte PowerShell'de iki adet romen rakamı fonksiyonu.

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
    {
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.