Aller au contenu

Écrire un nombre en chiffres romains⚓︎

On souhaite dans cet exercice écrire des nombres entiers strictement positifs en chiffres romains.

Cette notation a évolué au fil des siècles. On se propose ici d'utiliser la méthode fixée au Moyen-Âge en se limitant aux entiers inférieurs à \(5\,000\).

1. Symboles utilisés

Les symboles valides sont les suivants :

Valeur Symbole
\(1\) I
\(5\) V
\(10\) X
\(50\) L
\(100\) C
\(500\) D
\(1\,000\) M

Ainsi, MMXII (\(2012\)) est une écriture valide, AM non.

2. Ordre d'écriture

La représentation d'un nombre en chiffres romains se lit de gauche à droite. Dans le cas de base, on y rencontre des symboles de valeurs décroissantes.

Ainsi, XVI est l'écriture valide du nombre \(16\), VIX non.

⚠ La règle n°5 autorise toutefois certaines entorses à cette règle.

3. Notation additive

Dans le cas de base, les valeurs des symboles sont additionnées.

Ainsi LXXIII représente \(50+10+10+1+1+1=73\).

4. Répétitions

Le symbole M peut être répété autant de fois que nécessaire.

Tous les autres symboles ne peuvent pas être répétés plus de 3 fois (inclus).

Ainsi, MMMMVI (\(4\,006\)) est une écriture valide, MMMMIIIIII non.

🧐 Cette règle a été fixée au Moyen-Âge. L'écriture MMMMIIIIII était valide dans l'Antiquité !

5. Notation soustractive

Afin d'éviter les répétitions interdites, on s'autorise des entorses à la règle n°2.

On peut ainsi faire précéder les symboles :

  • X et V par un unique symbole I,
  • C et L par un unique symbole X,
  • M et D par un unique symbole C.

Dans ces cas, la valeur qui précède est soustraite à la valeur précédée.

On a donc les correspondances suivantes :

Symboles Valeurs
IV \(4\)
IX \(9\)
XL \(40\)
... ...

Ainsi, MCMXLI représente \(1\,000+900+40+1=1\,941\).

Écrire la fonction romain qui prend en argument un nombre entier strictement positif valeur et renvoie son écriture en chiffres romains.

Exemples
🐍 Console Python
>>> romain(4)
'IV'
>>> romain(5)
'V'
>>> romain(6)
'VI'
>>> romain(4042)
'MMMMXLII'
###
VALEURSpy-undCORR = [bksl-nl (1000, "M"),bksl-nl (900, "CM"),bksl-nl (500, "D"),bksl-nl (400, "CD"),bksl-nl (100, "C"),bksl-nl (90, "XC"),bksl-nl (50, "L"),bksl-nl (40, "XL"),bksl-nl (10, "X"),bksl-nl (9, "IX"),bksl-nl (5, "V"),bksl-nl (4, "IV"),bksl-nl (1, "I"),bksl-nl]bksl-nlbksl-nl# Testsbksl-nlassert romain(4) == "IV"bksl-nlassert romain(5) == "V"bksl-nlassert romain(6) == "VI"bksl-nlassert romain(4042) == "MMMMXLII"bksl-nlbksl-nl# Tests supplémentairesbksl-nldef romainpy-undcorr(valeur):bksl-nl resultat = ""bksl-nl for entier, symbole in VALEURSpy-undCORR:bksl-nl while valeur >= entier:bksl-nl valeur -= entierbksl-nl resultat += symbolebksl-nl return resultatbksl-nlbksl-nlbksl-nlfor valeur in range(3989, 5py-und001):bksl-nl attendu = romainpy-undcorr(valeur)bksl-nl print(valeur, attendu)bksl-nl assert romain(valeur) == attendu, f"Erreur en écrivant {valeur}"bksl-nlbksl-nl 5/5

def romain(valeur):bksl-nl ...bksl-nlbksl-nlbksl-nl# Testsbksl-nlassert romain(4) == "IV"bksl-nlassert romain(5) == "V"bksl-nlassert romain(6) == "VI"bksl-nlassert romain(4042) == "MMMMXLII"bksl-nlbksl-nlVALEURS = [bksl-nl (1000, "M"),bksl-nl (900, "CM"),bksl-nl (500, "D"),bksl-nl (400, "CD"),bksl-nl (100, "C"),bksl-nl (90, "XC"),bksl-nl (50, "L"),bksl-nl (40, "XL"),bksl-nl (10, "X"),bksl-nl (9, "IX"),bksl-nl (5, "V"),bksl-nl (4, "IV"),bksl-nl (1, "I"),bksl-nl]bksl-nlbksl-nlbksl-nldef romain(valeur):bksl-nl resultat = ""bksl-nl for entier, symbole in VALEURS:bksl-nl while valeur >= entier:bksl-nl valeur -= entierbksl-nl resultat += symbolebksl-nl return resultatbksl-nlbksl-nl

A

On procède de façon analogue au « Rendu de monnaie » : au lieu de rendre en priorité les « pièces » les plus grandes, ici, on écrit en priorité les « valeurs » les plus grandes.

📋 Explications
Créer une chaîne de caractère vide et l'affecter à la variable résultat
Pour chaque nombre utilisable :
    Tant que la valeur à convertir est supérieure ou égale à ce nombre :
        Concaténer l'écriture romaine de ce nombre au résultat
        Soustraire ce nombre à la valeur à convertir

On peut aussi procéder en utilisant des divisions euclidiennes afin de ne pas écrire de boucle while. On rappelle en effet que :

  • 1748 // 1000 est évalué à 1, c'est le quotient de la division euclidienne de \(1\,748\) par \(1\,000\) ;
  • 1748 % 1000 est évalué à 748, c'est le reste de cette même division.
🐍 Script Python
def romain(valeur):
    resultat = ""
    for entier, symbole in VALEURS:
        quotient = valeur // entier
        valeur = valeur % entier
        resultat += symbole * quotient  # concaténation de caractères
    return resultat

Il est aussi possible de travailler avec un dictionnaire {valeur: symbole}. Cette approche pose néanmoins la question de l'ordre du parcours d'un dictionnaire. Il est en effet indispensable de parcourir les couples (valeur, symbole) dans l'ordre décroissant des valeurs.

Depuis Python 3.7, les parcours des dictionnaires sont effectués dans l'ordre d'insertion des clés. On peut donc procéder ainsi :

🐍 Script Python
DICO_VALEURS = {
    1000: "M", 900: "CM", 500: "D", 400: "CD",
    100: "C", 90: "XC", 50: "L", 40: "XL",
    10: "X", 9: "IX", 5: "V", 4: "IV",
    1: "I"
}

def romain(valeur):
    resultat = ""
    for entier, symbole in DICO_VALEURS:
        while valeur >= entier:
            valeur -= entier
            resultat += DICO_VALEURS[entier]
    return resultat

Il faut toutefois garder à l'esprit que cette approche dépend de la version de Python utilisée : elle n'est pas généralisable ni à l'ensemble des versions de Python ni à tous les langages de programmation.

Et après ?

Les entiers supérieurs ou égaux à \(5\,000\) s'écrivent en utilisant une barre horizontale indiquant qu'il faut multiplier le facteur concerné par \(1\,000\).

Par exemple \(\overline{\text{VI}}\text{CCCVIII}\) représente \(6\,308\) car le \(\overline{\text{VI}}\) est multiplié par \(1\,000\).

Z

Astuce (1)

Penser à un algorithme glouton.

Astuce (2)

On pourra utiliser la liste VALEURS = [(1000, "M"), (900, "CM"), (500, "D"), ...] après l'avoir complétée.