Wie können Sie nur gültige römische Ziffern mit einem regulären Ausdruck abgleichen?

165

Als ich über mein anderes Problem nachdachte, entschied ich, dass ich nicht einmal einen regulären Ausdruck erstellen kann, der mit römischen Ziffern übereinstimmt (geschweige denn eine kontextfreie Grammatik, die sie generiert).

Das Problem besteht darin, nur gültige römische Ziffern abzugleichen. ZB ist 990 NICHT "XM", sondern "CMXC".

Mein Problem bei der Erstellung des regulären Ausdrucks besteht darin, dass ich zurückblicken muss, um bestimmte Zeichen zuzulassen oder nicht zuzulassen. Nehmen wir zum Beispiel Tausende und Hunderte.

Ich kann M {0,2} C? M zulassen (um 900, 1000, 1900, 2000, 2900 und 3000 zuzulassen). Wenn die Übereinstimmung jedoch auf CM ist, kann ich nicht zulassen, dass folgende Zeichen C oder D sind (da ich bereits bei 900 bin).

Wie kann ich das in einem regulären Ausdruck ausdrücken?
Wenn es in einer Regex einfach nicht ausgedrückt werden kann, ist es in einer kontextfreien Grammatik ausgedrückt?

Daniel Magliola
quelle

Antworten:

328

Sie können hierfür den folgenden regulären Ausdruck verwenden:

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

Wenn Sie es aufschlüsseln, wird M{0,4}der Abschnitt mit den Tausenden angegeben und im Grunde genommen auf zwischen 0und beschränkt 4000. Es ist relativ einfach:

   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}

Sie können natürlich M*eine beliebige Zahl (einschließlich Null) von Tausenden zulassen, wenn Sie größere Zahlen zulassen möchten.

Als nächstes ist (CM|CD|D?C{0,3})etwas komplexer, dies ist für den Hunderte-Abschnitt und deckt alle Möglichkeiten ab:

  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

Drittens (XC|XL|L?X{0,3})folgt den gleichen Regeln wie im vorherigen Abschnitt, jedoch für die Zehnerstelle:

 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

Und schließlich (IX|IV|V?I{0,3})ist die Einheiten Abschnitt, Handhabung 0durch 9und auch ähnlich wie die beiden vorherigen Abschnitte (römische Ziffern, trotz ihrer scheinbaren Seltsamkeit, folgen einigen logischen Regeln , wenn Sie herausfinden , was sie sind):

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

Denken Sie daran, dass dieser reguläre Ausdruck auch mit einer leeren Zeichenfolge übereinstimmt. Wenn Sie dies nicht möchten (und Ihre Regex-Engine modern genug ist), können Sie positive Rückblicke und Vorausschau verwenden:

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

(Die andere Alternative besteht darin, vorher zu überprüfen, ob die Länge nicht Null ist).

paxdiablo
quelle
12
Sollte es nicht M {0,3} sein?
Zitrone
3
Gibt es eine Lösung, um zu vermeiden, dass die leere Zeichenfolge übereinstimmt?
Facundo Casco
11
@Aashish: Als die Römer eine Macht waren, mit der man rechnen musste, MMMMwar der richtige Weg. Die Overbar-Darstellung erfolgte lange nachdem das Kernimperium in Stücke gefallen war.
Paxdiablo
2
@paxdiablo So habe ich festgestellt, dass mmmcm fehlschlägt. String 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)) -> dies wird für MMMCM / MMMM in Java zu false ausgewertet.
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

Tatsächlich ist Ihre Prämisse fehlerhaft. 990 IS "XM" sowie "CMXC".

Die Römer waren weit weniger besorgt über die "Regeln" als Ihr Lehrer der dritten Klasse. Solange es sich summierte, war es in Ordnung. Daher war "IIII" für 4 genauso gut wie "IV". Und "IIM" war für 998 völlig cool.

(Wenn Sie Probleme damit haben ... Denken Sie daran, dass englische Schreibweisen erst im 18. Jahrhundert formalisiert wurden. Bis dahin war es gut genug, solange der Leser es herausfinden konnte.)

James Curran
quelle
8
Klar, das ist cool. Aber mein "strenger Syntaxbedarf für Lehrer der dritten Klasse" macht meiner Meinung nach ein viel interessanteres Regex-Problem ...
Daniel Magliola
5
Guter Punkt James, man sollte ein strenger Autor sein, aber ein verzeihender Leser.
Corin
@Corin: aka Postels Robustheitsprinzip
jfs
13

Nur um es hier zu speichern:

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

Entspricht allen römischen Ziffern. Kümmert sich nicht um leere Zeichenfolgen (erfordert mindestens einen römischen Ziffernbuchstaben). Sollte in PCRE, Perl, Python und Ruby funktionieren.

Online Ruby-Demo: http://rubular.com/r/KLPR1zq3Hj

Online-Konvertierung: http://www.onlineconversion.com/roman_numerals_advanced.htm

Lächeln
quelle
2
Ich weiß nicht warum, aber die Hauptantwort hat bei Autotranslate-Listen in MemoQ nicht funktioniert. Diese Lösung funktioniert jedoch - ohne Start- / Endsymbole für Zeichenfolgen.
Orlando2bjr
1
@ orlando2bjr gerne helfen. Ja, in diesem Fall habe ich eine Nummer alleine ohne Umgebung gefunden. Wenn Sie in einem Text danach suchen, müssen Sie ^ ^ entfernen. Prost!
Smileart
12

Um zu vermeiden , den leeren String - Matching müssen Sie das Muster viermal wiederholen und ersetzen jeweils 0mit einem 1wiederum und machen V, Lund 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}))

In diesem Fall (da dieses Muster ^und verwendet $) sollten Sie zuerst nach leeren Zeilen suchen und sich nicht die Mühe machen, diese abzugleichen. Wenn Sie Wortgrenzen verwenden, haben Sie kein Problem, da es kein leeres Wort gibt. (Zumindest definiert Regex keinen; fang nicht an zu philosophieren, ich bin hier pragmatisch!)


In meinem speziellen Fall (in der realen Welt) brauchte ich übereinstimmende Ziffern an den Wortenden und fand keinen anderen Weg daran vorbei. Ich musste die Fußnotennummern aus meinem Nur-Text-Dokument entfernen , in das Text wie "Red Sea Cl und Great Barrier Reef Cli " konvertiert worden war the Red Seacl and the Great Barrier Reefcli. Aber ich hatte immer noch Probleme mit gültigen Wörtern wie Tahitiund werde fantasticin Tahitund geschrubbt fantasti.

Corin
quelle
Ich habe ein ähnliches Problem (!): Eine "linke Trimmung" der verbleibenden / verbleibenden römischen Nummer einer Artikelliste (HTML OL vom Typ I oder i) durchzuführen. Wenn also noch etwas übrig ist, muss ich (wie eine Trimmfunktion) mit Ihrem regulären Ausdruck am Anfang (links) des Elementtextes bereinigen ... Aber einfacher: Elemente werden niemals verwendet Moder Coder Lhaben Sie dies? Art von vereinfachtem Regex?
Peter Krauss
... ok, hier scheint es ok (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Peter Krauss
1
Sie müssen das Muster nicht wiederholen, um leere Zeichenfolgen abzulehnen. Sie könnten eine Lookahead-Behauptung verwenden
jfs
7

Glücklicherweise ist der Zahlenbereich auf ungefähr 1..3999 begrenzt. Daher können Sie das Regex-Stückgericht aufbauen.

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

Jeder dieser Teile wird sich mit den Launen der römischen Notation befassen. Beispiel: Verwenden der Perl-Notation:

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

Wiederholen und zusammenbauen.

Hinzugefügt : Das <opt-hundreds-part>kann weiter komprimiert werden:

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

Da die Klausel 'D? C {0,3}' mit nichts übereinstimmen kann, ist das Fragezeichen nicht erforderlich. Und höchstwahrscheinlich sollten die Klammern vom Typ ohne Erfassung sein - in Perl:

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

Natürlich sollte auch die Groß- und Kleinschreibung nicht berücksichtigt werden.

Sie können dies auch erweitern, um die von James Curran genannten Optionen zu behandeln (um XM oder IM für 990 oder 999 und CCCC für 400 usw. zuzulassen).

<opt-hundreds-part> = m/(?:[IXC][MD]|D?C{0,4})/;
Jonathan Leffler
quelle
Beginnend mit thousands hundreds tens unitsist es einfach , einen FSM
jfs
Was meinst du mit Glücklicherweise ist der Zahlenbereich auf 1..3999 oder so begrenzt ? Wer hat es begrenzt?
SexyBeast
@SexyBeast: Es gibt keine römische Standardnotation für 5.000, geschweige denn größere Zahlen, sodass die Regelmäßigkeiten, die bis dahin funktionieren, nicht mehr funktionieren.
Jonathan Leffler
Ich bin mir nicht sicher, warum Sie das glauben, aber römische Ziffern können Zahlen in Millionenhöhe darstellen. en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel
@ AmbroseChapel: Wie ich bereits sagte, gibt es keine (einzelne) Standardnotation für 5.000, geschweige denn größere Zahlen. Sie müssen eines von mehreren unterschiedlichen Systemen verwenden, wie in dem Wikipedia-Artikel beschrieben, auf den Sie verlinken, und Sie haben Probleme mit der Rechtschreibung des Systems mit Über-, Unter- oder umgekehrten C usw. Und Sie müssen jedem erklären, was System, das Sie verwenden und was es bedeutet; Menschen werden im Allgemeinen die römischen Ziffern jenseits von M nicht erkennen. Sie können sich dafür entscheiden, anders zu denken; Das ist Ihr Vorrecht, genauso wie es mein Vorrecht ist, zu meinen vorherigen Kommentaren zu stehen.
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'

Für Leute, die die Logik wirklich verstehen wollen, lesen Sie bitte eine schrittweise Erklärung auf 3 Seiten über Diveintopython .

Der einzige Unterschied zur ursprünglichen Lösung (die es gab M{0,4}) besteht darin, dass ich festgestellt habe, dass 'MMMM' keine gültige römische Ziffer ist (auch alte Römer haben höchstwahrscheinlich nicht über diese große Zahl nachgedacht und werden mir nicht zustimmen). Wenn Sie nicht mit alten Römern übereinstimmen, verzeihen Sie mir bitte und verwenden Sie die Version {0,4}.

Salvador Dali
quelle
1
Der reguläre Ausdruck in der Antwort erlaubt leere Ziffern. Wenn du es nicht willst; Sie können eine Lookahead-Behauptung verwenden , um leere Zeichenfolgen abzulehnen (sie ignoriert auch den Fall der Buchstaben).
JFS
2

Ich beantworte diese Frage Regulärer Ausdruck in Python für römische Ziffern hier,
weil er als genaues Duplikat dieser Frage markiert wurde.

Der Name mag ähnlich sein, aber dies ist eine spezifische Regex-Frage / ein Problem,
wie aus dieser Antwort auf diese Frage hervorgeht.

Die gesuchten Elemente können zu einer einzigen Abwechslung zusammengefasst und dann
in eine Erfassungsgruppe eingeschlossen werden, die mit der
Funktion findall () in eine Liste aufgenommen wird .
Es wird so gemacht:

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

Die Regex-Modifikationen zum Faktorisieren und Erfassen nur der Ziffern lauten wie folgt:

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

Wie Jeremy und Pax oben ausgeführt haben ... '^ M {0,4} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0,3}) $ 'sollte die Lösung sein, nach der Sie suchen ...

Die spezifische URL, die angehängt werden sollte (IMHO), lautet http://thehazeltree.org/diveintopython/7.html

Beispiel 7.8 ist die Kurzform mit {n, m}

Jonathan Leffler
quelle
1

In meinem Fall habe ich versucht, alle Vorkommen römischer Zahlen durch ein Wort im Text zu finden und zu ersetzen, sodass ich den Anfang und das Ende von Zeilen nicht verwenden konnte. Die @ paxdiablo-Lösung hat also viele Übereinstimmungen mit der Länge Null gefunden. Am Ende hatte ich folgenden Ausdruck:

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

Mein letzter Python-Code war wie folgt:

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)

Ausgabe:

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

Steven Levithan verwendet diesen regulären Ausdruck in seinem Beitrag, der römische Ziffern validiert, bevor der Wert "deromanisiert" wird:

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

Ich habe mehrere Antworten gesehen, die keine leeren Zeichenfolgen abdecken oder Lookaheads verwenden, um dies zu lösen. Und ich möchte eine neue Antwort hinzufügen, die leere Zeichenfolgen abdeckt und keinen Lookahead verwendet. Der reguläre Ausdruck ist der folgende:

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

Ich erlaube unendlich M, M+aber natürlich könnte jemand ändern M{1,4}, um nur 1 oder 4 zuzulassen, wenn dies gewünscht wird.

Im Folgenden finden Sie eine Visualisierung, die Ihnen hilft, die Funktionsweise zu verstehen. Vorangegangen sind zwei Online-Demos:

Debuggex-Demo

Regex 101 Demo

Visualisierung regulärer Ausdrücke

Bernardo Duarte
quelle
0

Dies funktioniert in Java- und PCRE-Regex-Engines und sollte jetzt im neuesten JavaScript funktionieren, funktioniert jedoch möglicherweise nicht in allen Kontexten.

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

Der erste Teil ist das grausame negative Aussehen. Aus logischen Gründen ist es jedoch am einfachsten zu verstehen. Grundsätzlich (?<!)sagt der erste, dass er nicht mit der Mitte übereinstimmt, ([MATCH])wenn Buchstaben vor der Mitte stehen, ([MATCH])und der letzte (?!)sagt, dass er nicht mit der Mitte übereinstimmt([MATCH]) wenn Buchstaben danach kommen.

Die Mitte ([MATCH])ist nur der am häufigsten verwendete Regex, um die Reihenfolge der römischen Ziffern abzugleichen. Aber jetzt wollen Sie das nicht mehr zusammenbringen, wenn es Buchstaben gibt.

Überzeugen Sie sich selbst. https://regexr.com/4vce5

Ketenks
quelle
-1

Das Problem der Lösung von Jeremy und Pax ist, dass sie auch mit "nichts" übereinstimmt.

Der folgende reguläre Ausdruck erwartet mindestens eine römische Ziffer:

^(M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|[IDCXMLV])$
Marvin Frommhold
quelle
6
Diese Funktion funktioniert nicht (es sei denn, Sie verwenden eine sehr seltsame Regex-Implementierung). Der linke Teil der |kann mit einer leeren Zeichenfolge und allen gültigen römischen Ziffern übereinstimmen, sodass die rechte Seite vollständig redundant ist. und ja, es stimmt immer noch mit einer leeren Zeichenfolge überein.
DirtY iCE
"Das Problem der Lösung von Jeremy und Pax ist" ... genau das gleiche wie das Problem, das diese Antwort hat. Wenn Sie eine Lösung für ein vermeintliches Problem vorschlagen möchten, sollten Sie diese wahrscheinlich testen. :-)
paxdiablo
Ich habe leere Zeichenfolge damit
Aminah Nuraini
-2

Ich würde Funktionen für meine Arbeit für mich schreiben. Hier sind zwei Funktionen für römische Zahlen in 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
    {
    }
}
Vince Ypma
quelle