Comment faire correspondre uniquement des chiffres romains valides avec une expression régulière?

165

En pensant à mon autre problème , j'ai décidé que je ne pouvais même pas créer une expression régulière qui correspondrait aux chiffres romains (sans parler d'une grammaire sans contexte qui les générera)

Le problème correspond uniquement à des chiffres romains valides. Par exemple, 990 n'est PAS "XM", c'est "CMXC"

Mon problème en créant l'expression régulière pour cela est que pour autoriser ou non certains caractères, je dois regarder en arrière. Prenons des milliers et des centaines, par exemple.

Je peux autoriser M {0,2} C? M (pour permettre 900, 1000, 1900, 2000, 2900 et 3000). Cependant, si la correspondance est sur CM, je ne peux pas autoriser les caractères suivants à être C ou D (car je suis déjà à 900).

Comment puis-je exprimer cela dans une expression régulière?
Si elle n'est tout simplement pas exprimable dans une expression régulière, est-elle exprimable dans une grammaire sans contexte?

Daniel Magliola
la source

Réponses:

328

Vous pouvez utiliser l'expression régulière suivante pour cela:

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

Le décomposer, M{0,4}spécifie la section des milliers et la restreint essentiellement entre 0et 4000. C'est un moyen relativement simple:

   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}

Vous pouvez, bien sûr, utiliser quelque chose comme M*autoriser n'importe quel nombre (y compris zéro) de milliers, si vous voulez autoriser des nombres plus grands.

Ensuite (CM|CD|D?C{0,3}), un peu plus complexe, c'est pour la section des centaines et couvre toutes les possibilités:

  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

Troisièmement, (XC|XL|L?X{0,3})suit les mêmes règles que la section précédente mais pour la place des dizaines:

 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

Et, enfin, (IX|IV|V?I{0,3})la section des unités, traitant à 0travers 9et également similaire aux deux sections précédentes (les chiffres romains, malgré leur apparente bizarrerie, suivent certaines règles logiques une fois que vous avez compris ce qu'ils sont):

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

Gardez simplement à l'esprit que cette expression régulière correspondra également à une chaîne vide. Si vous ne le souhaitez pas (et que votre moteur regex est suffisamment moderne), vous pouvez utiliser une anticipation et une anticipation positives:

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

(l'autre alternative étant simplement de vérifier au préalable que la longueur n'est pas nulle).

paxdiablo
la source
12
Ne devrait-il pas être M {0,3}?
citron
3
une solution pour éviter de faire correspondre la chaîne vide?
Facundo Casco
11
@Aashish: Quand les Romains étaient une force avec laquelle il fallait compter, MMMMc'était la bonne voie. La représentation overbar est venue longtemps après la chute de l'empire central.
paxdiablo
2
@paxdiablo c'est ainsi que j'ai trouvé que mmmcm échouait. Chaîne 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)) -> cela évalue à false pour MMMCM / MMMM en 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

En fait, votre prémisse est imparfaite. 990 EST "XM", ainsi que "CMXC".

Les Romains étaient beaucoup moins préoccupés par les «règles» que votre professeur de troisième année. Tant que cela s'additionnait, tout allait bien. Donc "IIII" était aussi bon que "IV" pour 4. Et "IIM" était complètement cool pour 998.

(Si vous avez du mal à gérer cela ... Rappelez-vous que les orthographes anglaises n'étaient pas formalisées avant les années 1700. Jusque-là, tant que le lecteur pouvait le comprendre, c'était assez bon).

James Curran
la source
8
Bien sûr, c'est cool. Mais mon besoin de syntaxe de "professeur de troisième année stricte" fait un problème de regex beaucoup plus intéressant, à mon avis ...
Daniel Magliola
5
Bon point James, il faut être un auteur strict mais un lecteur indulgent.
Corin
13

Juste pour le sauvegarder ici:

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

Correspond à tous les chiffres romains. Ne se soucie pas des chaînes vides (nécessite au moins une lettre en chiffres romains). Devrait fonctionner en PCRE, Perl, Python et Ruby.

Démo Ruby en ligne: http://rubular.com/r/KLPR1zq3Hj

Conversion en ligne: http://www.onlineconversion.com/roman_numerals_advanced.htm

smileart
la source
2
Je ne sais pas pourquoi, mais la réponse principale n'a pas fonctionné pour moi dans les listes de traduction automatique dans MemoQ. Cependant, cette solution le fait - à l'exclusion des symboles de début / fin de chaîne.
orlando2bjr
1
@ orlando2bjr heureux de vous aider. Ouais, dans ce cas, je correspondais à un numéro seul, sans environnement. Si vous le recherchez dans un texte, vous devez certainement supprimer ^ $. À votre santé!
smileart
12

Pour éviter de faire correspondre la chaîne vide, vous devrez répéter le modèle quatre fois et remplacer chacun 0par un 1tour à tour, et prendre en compte V, Let 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}))

Dans ce cas (car ce modèle utilise ^et $), vous feriez mieux de vérifier d'abord les lignes vides et de ne pas vous soucier de les faire correspondre. Si vous utilisez des limites de mots, vous n'avez pas de problème car il n'y a pas de mot vide. (Au moins regex n'en définit pas un; ne commencez pas à philosopher, je suis pragmatique ici!)


Dans mon propre cas (dans le monde réel), j'avais besoin de faire correspondre les chiffres aux fins des mots et je n'ai trouvé aucun autre moyen de contourner cela. Je avais besoin de frotter au large des numéros de note de bas de mon document de texte brut, où le texte tel que « la mer Rouge cl et la Grande Barrière de Corail cli » avait été converti en the Red Seacl and the Great Barrier Reefcli. Mais j'ai toujours eu des problèmes avec des mots valides comme Tahitiet fantasticsont frottés dans Tahitet fantasti.

Corin
la source
J'ai un problème similaire (!): Faire un "trim à gauche" du nombre romain restant / résiduel d'une liste d'éléments (HTML OL de type I ou i). Donc, quand il y a reste, je dois nettoyer (comme une fonction TRIM) avec votre regex au début ( à gauche) de l'élément texte ... Mais plus simple: les articles ne jamais utiliser Mou Cou L, alors, avez - vous cette sorte de regex simplifiée?
Peter Krauss du
... ok, ici ça semble ok (!),(X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))
Peter Krauss
1
vous n'avez pas besoin de répéter le modèle, de rejeter les chaînes vides. Vous pouvez utiliser une assertion
anticipée
7

Heureusement, la plage de nombres est limitée à 1..3999 ou à peu près. Par conséquent, vous pouvez construire le morceau-repas regex.

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

Chacune de ces parties traitera des caprices de la notation romaine. Par exemple, en utilisant la notation Perl:

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

Répétez et assemblez.

Ajouté : Le <opt-hundreds-part>peut être compressé davantage:

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

Puisque la clause 'D? C {0,3}' ne peut correspondre à rien, le point d'interrogation n'est pas nécessaire. Et, très probablement, les parenthèses devraient être du type non capturant - en Perl:

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

Bien sûr, tout doit également être insensible à la casse.

Vous pouvez également l'étendre pour traiter les options mentionnées par James Curran (pour autoriser XM ou IM pour 990 ou 999, et CCCC pour 400, etc.).

<opt-hundreds-part> = m/(?:[IXC][MD]|D?C{0,4})/;
Jonathan Leffler
la source
En commençant par thousands hundreds tens units, il est facile de créer un FSM qui calcule et valide des chiffres romains donnés
jfs
Qu'entendez-vous par Heureusement, la plage de nombres est limitée à 1..3999 ou à peu près ? Qui l'a limité?
SexyBeast
@SexyBeast: Il n'y a pas de notation romaine standard pour 5000, sans parler de plus grands nombres, donc les régularités qui fonctionnent jusqu'à ce moment-là cessent de fonctionner.
Jonathan Leffler le
Je ne sais pas pourquoi vous croyez cela, mais les chiffres romains peuvent représenter des nombres en millions. en.wikipedia.org/wiki/Roman_numerals#Large_numbers
AmbroseChapel
@AmbroseChapel: Comme je l'ai dit, il n'y a pas de notation standard (unique) pour 5000, et encore moins de plus grands nombres. Vous devez utiliser l'un des nombreux systèmes divergents décrits dans l'article de Wikipédia auquel vous créez un lien, et vous rencontrez des problèmes d'orthographe pour le système avec overbars, underbars ou C inversé, etc. Et vous devrez expliquer à n'importe qui ce que système que vous utilisez et ce que cela signifie; les gens ne reconnaîtront pas, en général, les chiffres romains au-delà de M. Vous pouvez choisir de penser autrement; c'est votre prérogative, tout comme c'est ma prérogative de m'en tenir à mes commentaires précédents.
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'

Pour les personnes qui veulent vraiment comprendre la logique, veuillez consulter une explication étape par étape sur 3 pages sur diveintopython .

La seule différence par rapport à la solution originale (qui avait M{0,4}) est que j'ai trouvé que «MMMM» n'est pas un chiffre romain valide (aussi les anciens Romains n'ont probablement pas pensé à ce nombre énorme et seront en désaccord avec moi). Si vous faites partie de vieux Romains en désaccord, veuillez me pardonner et utiliser la version {0,4}.

Salvador Dali
la source
1
l'expression régulière dans la réponse autorise les chiffres vides. Si vous ne le voulez pas; vous pouvez utiliser une assertion anticipée , pour rejeter les chaînes vides (elle ignore également la casse des lettres).
jfs
2

Je réponds à cette question Expression régulière en Python pour les chiffres romains ici
car elle a été marquée comme un double exact de cette question.

Il peut être similaire en nom, mais il s'agit d'une question / problème spécifique aux expressions régulières,
comme le montre cette réponse à cette question.

Les éléments recherchés peuvent être combinés en une seule alternance puis
enfermés dans un groupe de capture qui sera mis dans une liste avec la fonction findall ()
.
C'est fait comme ceci:

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

Les modifications de regex pour factoriser et capturer uniquement les chiffres sont les suivantes:

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

Comme Jeremy et Pax l'ont souligné plus haut ... '^ M {0,4} (CM | CD | D? C {0,3}) (XC | XL | L? X {0,3}) (IX | IV | V? I {0,3}) $ 'devrait être la solution que vous recherchez ...

L'URL spécifique qui aurait dû être jointe (IMHO) est http://thehazeltree.org/diveintopython/7.html

L'exemple 7.8 est la forme courte utilisant {n, m}

Jonathan Leffler
la source
1

Dans mon cas, j'essayais de trouver et de remplacer toutes les occurrences de nombres romains par un mot à l'intérieur du texte, donc je ne pouvais pas utiliser le début et la fin des lignes. La solution @paxdiablo a donc trouvé de nombreuses correspondances de longueur nulle. J'ai fini avec l'expression suivante:

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

Mon code Python final était comme ceci:

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)

Production:

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

Steven Levithan utilise cette regex dans son article qui valide les chiffres romains avant de "déromaniser" la valeur:

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

J'ai vu plusieurs réponses qui ne couvrent pas les chaînes vides ou utilisent des lookaheads pour résoudre ce problème. Et je veux ajouter une nouvelle réponse qui couvre les chaînes vides et n'utilise pas lookahead. L'expression régulière est la suivante:

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

J'autorise l'infini M, M+mais bien sûr, quelqu'un pourrait changer M{1,4}pour n'autoriser que 1 ou 4 si vous le souhaitez.

Vous trouverez ci-dessous une visualisation qui aide à comprendre ce qu'il fait, précédée de deux démos en ligne:

Démo Debuggex

Démo Regex 101

Visualisation des expressions régulières

Bernardo Duarte
la source
0

Cela fonctionne dans les moteurs de regex Java et PCRE et devrait maintenant fonctionner dans le dernier JavaScript, mais peut ne pas fonctionner dans tous les contextes.

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

La première partie est l'atroce regard négatif en arrière. Mais, pour des raisons logiques, c'est le plus facile à comprendre. Fondamentalement, le premier (?<!)dit ne pas correspondre au milieu ([MATCH])s'il y a des lettres avant le milieu ([MATCH])et le dernier (?!)dit ne pas correspondre au milieu([MATCH]) s'il y a des lettres après.

Le milieu ([MATCH])est juste l'expression régulière la plus couramment utilisée pour faire correspondre la séquence de chiffres romains. Mais maintenant, vous ne voulez pas faire correspondre cela s'il y a des lettres autour.

Voir par vous-même. https://regexr.com/4vce5

ketenks
la source
-1

Le problème de la solution de Jeremy et Pax est qu'elle ne correspond pas à "rien".

L'expression régulière suivante attend au moins un chiffre romain:

^(M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})|[IDCXMLV])$
Marvin Frommhold
la source
6
celui-là ne fonctionnera pas (sauf si vous utilisez une implémentation de regex très étrange) - la partie gauche de |peut correspondre à une chaîne vide et à tous les chiffres romains valides, donc le côté droit est complètement redondant. et oui, il correspond toujours à une chaîne vide.
DirtY iCE
"Le problème de la solution de Jeremy et Pax est" ... exactement le même que le problème de cette réponse. Si vous proposez une solution à un problème supposé, vous devriez probablement la tester. :-)
paxdiablo
J'ai une chaîne vide avec ça
Aminah Nuraini
-2

J'écrirais des fonctions à mon travail pour moi. Voici deux fonctions de chiffres romains dans 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
la source