web-dev-qa-db-fra.com

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

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 (encore moins à une grammaire sans contexte qui les générera)

Le problème ne concerne que les chiffres romains valides. Par exemple, 990 n'est pas "XM", c'est "CMXC"

Mon problème en faisant la regex pour ceci est que pour autoriser ou ne pas autoriser certains caractères, je dois regarder en arrière… .. Prenons des milliers et des centaines, par exemple.

Je peux accepter 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 rationnelle?
Si ce n'est tout simplement pas exprimable dans une regex, est-il exprimable dans une grammaire sans contexte?

144
Daniel Magliola

Essayer:

^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}

Ceci spécifie la section des milliers et la limite essentiellement entre 0 et 4000. C'est un 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}

(CM|CD|D?C{0,3})

Un peu plus complexe, cela concerne 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

(XC|XL|L?X{0,3})

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

(IX|IV|V?I{0,3})

Il s'agit de la section des unités, qui gère 0 à 9 et qui est semblable aux deux sections précédentes (les chiffres romains, malgré leur apparence étrange, suivent certaines règles logiques une fois que vous avez déterminé 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
304
paxdiablo

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

Les Romains étaient beaucoup moins préoccupés par les "règles" que votre enseignant de troisième année. Tant que ça a fonctionné, ça allait. Par conséquent, "IIII" était aussi bon que "IV" pour 4. Et "IIM" était complètement cool pour 998.

(Si vous avez du mal à gérer cela ... Souvenez-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 suffisant.

19
James Curran

Pour éviter de faire correspondre la chaîne vide, vous devrez répéter le motif quatre fois et remplacer chaque 0 par un 1 à tour de rôle, en tenant compte de V, L et 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 $), il serait préférable de vérifier les lignes vides en premier et de ne pas les faire correspondre. Si vous utilisez Limites de mots alors vous n’avez pas de problème car il n’existe pas de mot vide. (Au moins, l'expression rationnelle n'en définit pas; ne commence pas à philosopher, je suis pragmatique ici!)


Dans mon cas particulier (monde réel), j'avais besoin de faire correspondre les chiffres aux extrémités de Word et je n'ai trouvé aucun autre moyen de les contourner. Je devais effacer les numéros de note de bas de page de mon document en texte brut, où texte tel que "la mer Rougecl et la grande barrière de corailcli"avait été converti en the Red Seacl and the Great Barrier Reefcli. Mais j’ai toujours eu des problèmes avec des mots valides tels que Tahiti et fantastic sont nettoyés en Tahit et fantasti.

11
Corin

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 sous PCRE, Perl, Python et Ruby.

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

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

9
smileart

Heureusement, la plage de numéros est limitée à 1..3999 ou à peu près. Par conséquent, vous pouvez construire le regex à la pièce.

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

Chacune de ces parties traitera des aléas 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éter et assembler.

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 correspond à rien, le point d'interrogation n'est pas nécessaire. Et, très probablement, les parenthèses devraient être du type sans capture - en Perl:

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

Bien sûr, tout cela devrait également être sensible à la casse.

Vous pouvez également étendre cette option aux options mentionnées par James Curran (autoriser XM ou IM pour 990 ou 999 et CCCC pour 400, etc.).

<opt-hundreds-part> = m/(?:[IXC][MD]|D?C{0,4})/;
7
Jonathan Leffler
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, jetez un œil à une explication étape par étape sur 3 pages de 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 (les vieux Romains aussi n’ont probablement pas pensé à ce nombre énorme et ne seront pas d’accord avec moi). Si vous êtes un des vieux Romains en désaccord, pardonnez-moi et utilisez la version {0,4}.

4
Salvador Dali

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

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

L'exemple 7.8 est la forme abrégée utilisant {n, m} 

1
qcha0s

Steven Levithan utilise cette expression rationnelle dans son message 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])$/
0
Mottie

Dans mon cas, j'essayais de trouver et de remplacer toutes les occurrences de chiffres romains par un mot dans le texte, de sorte que je ne pouvais utiliser ni le début ni la fin des lignes. Donc, la solution @paxdiablo a trouvé beaucoup de correspondances de longueur nulle . Je me suis retrouvé 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 ça:

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)

Sortie:

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

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

La regex 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])$
0
Marvin Frommhold