Roman Numerals¶

First, we have to read in all the Roman numerals we're working with.

In [1]:
with open("txt/0089_roman.txt") as f:
    numerals = [s.strip() for s in f.readlines()]

To assist us, we'll make a map from denominations to Roman numerals.

In [2]:
denominations = {
    1: "I",
    2: "II",
    3: "III",
    4: "IV",
    5: "V",
    6: "VI",
    7: "VII",
    8: "VIII",
    9: "IX",
    10: "X",
    20: "XX",
    30: "XXX",
    40: "XL",
    50: "L",
    60: "LX",
    70: "LXX",
    80: "LXXX",
    90: "XC",
    100: "C",
    200: "CC",
    300: "CCC",
    400: "CD",
    500: "D",
    600: "DC",
    700: "DCC",
    800: "DCCC",
    900: "CM",
    1000: "M",
}

We can make a simple greedy parser to convert the given Roman numerals to ints.

In [3]:
def to_int(s):
    rev_denominations = {v: k for (k, v) in denominations.items()}
    prefixes = sorted(rev_denominations.keys(), key=len, reverse=True)
    
    n = 0
    while s != "":
        for prefix in prefixes:
            if s.startswith(prefix):
                n += rev_denominations[prefix]
                s = s.removeprefix(prefix)
                break
                
    return n

It's arguably easier to convert an int to the minimal Roman numeral.

In [4]:
def to_roman(n):
    s = ["", "", "", ""]
    
    for i in range(1, 4):
        d = n % 10^i
        if d in denominations:
            s[-i] = denominations[d]
    
        n -= d
        
    s[0] = "M" * (n // 1000)
    
    return "".join(s)

To solve, convert each Roman numeral to an int, then convert that int to the minimal Roman numeral, and count the characters saved.

In [5]:
total = 0
for numeral in numerals:
    value = to_int(numeral)
    minimal = to_roman(value)
    total += len(numeral) - len(minimal)

total
Out[5]:
743

Copyright (C) 2025 filifa¶

This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license and the BSD Zero Clause license.