182 votes

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

Réflexion sur 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érerait).

Le problème est de ne faire correspondre que les chiffres romains valides. Par exemple, 990 n'est PAS "XM", c'est "CMXC".

Le problème que je rencontre en créant la regex pour cela est que pour autoriser ou non certains caractères, je dois revenir en arrière. Prenons l'exemple de milliers et de centaines.

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

Comment puis-je exprimer cela dans une regex ?
Si ce n'est tout simplement pas exprimable dans une regex, est-ce exprimable dans une grammaire sans contexte ?

353voto

paxdiablo Points 341644

Vous pouvez utiliser la regex suivante pour cela :

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

La décomposition, M{0,4} spécifie la section des milliers et la restreint essentiellement entre 0 y 4000 . C'est 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 pourriez, bien sûr, utiliser quelque chose comme M* pour permettre tout nombre (y compris le zéro) de milliers, si vous voulez autoriser des nombres plus grands.

Le suivant est (CM|CD|D?C{0,3}) Un peu plus complexe, elle est destinée à 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}) est la section des unités, la manipulation 0 par le biais de 9 et également similaire aux deux sections précédentes (les chiffres romains, malgré leur bizarrerie apparente, suivent certaines règles logiques une fois que vous avez compris de quoi il s'agit) :

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 à l'esprit que cette expression rationnelle correspondra également à une chaîne vide. Si vous ne voulez pas cela (et que votre moteur de regex est suffisamment moderne), vous pouvez utiliser un look-behind et un look-ahead positifs :

(?<=^)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 de simplement vérifier au préalable que la longueur n'est pas nulle).

0 votes

C'est possible. Je ne vois que des inférences dans la question indiquant qu'elle doit aller jusqu'à 3999 (et non une exigence précise et définie) mais je l'autorise quand même jusqu'à 4999. Si vous voulez vraiment le limiter à 3999, alors il faut absolument supprimer l'une des M.

3 votes

Une solution pour éviter la correspondance avec la chaîne vide ?

0 votes

Oui, vous pouvez utiliser l'un de ces dispositifs de prévision si votre moteur de regex le supporte, ou vous pouvez simplement vérifier que la longueur est supérieure à zéro.

27voto

James Curran Points 55356

En fait, votre prémisse est erronée. 990 IS "XM", ainsi que "CMXC".

Les Romains étaient bien moins concernés par les "règles" que votre professeur de CE2. Tant que l'addition était faite, c'était bon. Donc "IIII" était aussi bon que "IV" pour 4. Et "IIM" était complètement cool pour 998.

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

10 votes

Bien sûr, c'est cool. Mais mon besoin de syntaxe "strict de professeur de troisième année" fait un problème de regex beaucoup plus intéressant, à mon avis...

7 votes

Bon point James, il faut être un auteur strict mais un lecteur indulgent.

0 votes

13voto

Corin Points 1419

Pour éviter de faire correspondre la chaîne de caractères vide, vous devrez répéter le motif quatre fois et remplacer chaque 0 avec un 1 à leur tour, et tenir compte de V , L y 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 (parce que ce modèle utilise ^ y $ ), vous feriez mieux de vérifier d'abord si les lignes sont vides et de ne pas prendre la peine de les faire correspondre. Si vous utilisez limites des mots alors vous n'avez pas de problème car il n'y a pas de mot vide. (Du moins, les regex n'en définissent pas ; ne commencez pas à philosopher, je suis pragmatique ici).


Dans mon cas particulier (monde réel), j'avais besoin de faire correspondre les chiffres à la fin des mots et je n'ai pas trouvé d'autre solution. J'avais besoin d'effacer les numéros de notes de bas de page de mon document en texte brut, où du texte tel que "la mer Rouge" était présent. cl et la Grande Barrière de Corail cli " avait été converti en the Red Seacl and the Great Barrier Reefcli . Mais j'avais encore des problèmes avec des mots valides comme Tahiti y fantastic sont nettoyés dans Tahit y fantasti .

0 votes

J'ai un problème similaire ( !): faire une "coupe à gauche" des numéros romains restants/résiduels d'une liste d'éléments (HTML OL de type I ou i). Donc, quand il y en a qui restent, j'ai besoin de nettoyer (comme une fonction trim) avec votre regex au début (gauche) du texte de l'article... Mais plus simple : les articles n'utilisent jamais M o C o L alors, avez-vous ce genre de regex simplifiée ?

0 votes

... ok, ici ça semble correct ( !), (X{1,3}(IX|IV|V?I{0,3})|X{0,3}(IX|I?V|V?I{1,3}))

1 votes

Vous n'avez pas besoin de répéter le motif, pour rejeter les chaînes vides. Vous pourriez utiliser une assertion de type lookahead

11voto

Jeremy Ruten Points 59989

8voto

Jonathan Leffler Points 299946

Heureusement, la plage des nombres est limitée à 1..3999 ou à peu près. Par conséquent, vous pouvez construire la regex au coup par coup.

<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étez et assemblez.

Ajouté : Le site <opt-hundreds-part> peuvent être comprimés 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 non capturant - en Perl :

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

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

Vous pouvez également étendre ce système 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})/;

0 votes

En commençant par thousands hundreds tens units il est facile de créer un FSM qui calcule et valide en chiffres romains

0 votes

Que voulez-vous dire par Heureusement, la plage de numéros est limitée à 1..3999 ou à peu près. ? Qui l'a limité ?

0 votes

@SexyBeast : Il n'y a pas de notation romaine standard pour 5 000, sans parler des nombres plus grands, donc les régularités qui fonctionnent jusqu'à ce moment-là cessent de fonctionner.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X