DEV Community

Shahrouz Nikseresht
Shahrouz Nikseresht

Posted on

Day 42: Python Roman Numeral Converter, Bidirectional Conversion Between Roman and Integers with Mapping and Loops

Welcome to Day 42 of the #80DaysOfChallenges journey! This intermediate challenge tackles converting between Roman numerals and integers in both directions, supporting ranges from 1 to 3999 with dictionary mappings for values, subtraction rules for Roman parsing, and iterative subtraction for integer to Roman. It combines string iteration, conditional logic for special cases like IV or CM, and user choice for mode, making it a robust exercise in bidirectional translation and input handling. If you're progressing from basic strings to more structured conversions or interested in historical number systems, this "Python Roman converter" script showcases functions that are accurate, efficient for the range, and extensible to larger values or validation.


💡 Key Takeaways from Day 42: Roman-Integer Conversion Functions

This task includes two core functions for each direction and a main block for interactivity with mode selection. It's a balanced demo of mapping-based lookups: dict for Roman values, list of tuples for integer subtraction pairs. We'll detail: roman_to_int with reverse iteration for subtraction, int_to_roman with greedy subtraction, and main with choice and bounds check.

1. Roman to Int: Reverse Loop for Subtraction Rules

The roman_to_int function parses a Roman string from right to left, adding or subtracting based on values:

def roman_to_int(s: str) -> int:
    """
    Convert a Roman numeral string to an integer.
    Reads the string from right to left and applies subtraction rules.
    """
    roman = {"I":1, "V":5, "X":10, "L":50, "C":100, "D":500, "M":1000}
    total = 0
    prev = 0

    for char in reversed(s):      # Read from last character to first
        value = roman[char]
        if value < prev:           # Subtract if smaller than previous
            total -= value
        else:                      # Otherwise add
            total += value
        prev = value

    return total
Enter fullscreen mode Exit fullscreen mode

Dict provides O(1) value lookups. Reversed loop handles subtractive notation (e.g., IV=4: V=5 add, I<5 subtract). For "MCMXCIV": V=5 add, I<5 subtract 1 (4), C=100 add, X<100 subtract 10 (90), M=1000 add, C<1000 subtract 100 (900), M=1000 add = 1994. Raises KeyError on invalid chars, caught in main.

2. Int to Roman: Greedy Subtraction with Pairs

The int_to_roman function builds Roman by subtracting largest possible values:

def int_to_roman(num: int) -> str:
    """
    Convert an integer (1–3999) to a Roman numeral string.
    Iteratively subtracts values and appends symbols.
    """
    vals = [
        (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")
    ]
    result = ""
    for value, symbol in vals:
        while num >= value:
            result += symbol
            num -= value
    return result
Enter fullscreen mode Exit fullscreen mode

Tuple list ordered descending, includes subtractives like CM=900. While subtracts and appends. For 1994: M (1000), num=994; CM (900), num=94; XC (90), num=4; IV (4), num=0 = "MCMXCIV". Efficient, O(1) since fixed pairs.

3. Main Interactive: Mode Choice and Validation

Script prompts for mode and input, handles errors:

print("\n=== Roman <-> Integer Converter ===")
choice = input("1: Roman -> Integer\n2: Integer -> Roman\nChoose (1 or 2): ")

if choice == "1":
    roman = input("Enter a Roman numeral (e.g., MCMXCIV): ").strip().upper()
    try:
        print(f"Result: {roman_to_int(roman)}")
    except KeyError:
        print("Invalid Roman numeral!")

elif choice == "2":
    num = int(input("Enter an integer (1–3999): "))
    if 1 <= num <= 3999:
        print(f"Roman numeral: {int_to_roman(num)}")
    else:
        print("Number must be between 1 and 3999!")
else:
    print("Invalid choice!")
Enter fullscreen mode Exit fullscreen mode

Upper/strip normalizes Roman. Try catches invalid chars. For int, bounds 1-3999 (standard Roman limit). Simple CLI, could loop for multiples.


🎯 Summary and Reflections

This Roman converter merges historical notation with modern code, using mappings for both directions. It reinforced:

  • Mapping power: Dict for lookup, tuples for ordered subtraction.
  • Direction differences: Reverse for parse, greedy for build.
  • Validation key: Bounds and try for robustness.

Romans limited to 3999, but script faithful. For fun, add beyond with overlines.

Advanced Alternatives: Regex validate Roman. Math digit for int_to_roman. Your conversion? Share!


🚀 Next Steps and Resources

Day 42 bridged history and code, prepping for encodings. In #80DaysOfChallenges? Added validation? Post!

Top comments (0)