Basic program to convert integer to Roman numerals?

后端 未结 24 1383
孤独总比滥情好
孤独总比滥情好 2020-11-30 11:52

I\'m trying to write a code that converts a user-inputted integer into its Roman numeral equivalent. What I have so far is:

The point of the generate_

24条回答
  •  天涯浪人
    2020-11-30 12:34

    I have produced an answer that works for any int >= 0:

    Save the following as romanize.py

    def get_roman(input_number: int, overline_code: str = '\u0305') -> str:
        """
        Recursive function which returns roman numeral (string), given input number (int)
    
        >>> get_roman(0)
        'N'
        >>> get_roman(3999)
        'MMMCMXCIX'
        >>> get_roman(4000)
        'MV\u0305'
        >>> get_roman(4000, overline_code='^')
        'MV^'
        """
        if input_number < 0 or not isinstance(input_number, int):
            raise ValueError(f'Only integers, n, within range, n >= 0 are supported.')
        if input_number <= 1000:
            numeral, remainder = core_lookup(input_number=input_number)
        else:
            numeral, remainder = thousand_lookup(input_number=input_number, overline_code=overline_code)
        if remainder != 0:
            numeral += get_roman(input_number=remainder, overline_code=overline_code)
        return numeral
    
    
    def core_lookup(input_number: int) -> (str, int):
        """
        Returns highest roman numeral (string) which can (or a multiple thereof) be looked up from number map and the
        remainder (int).
    
        >>> core_lookup(3)
        ('III', 0)
        >>> core_lookup(999)
        ('CM', 99)
        >>> core_lookup(1000)
        ('M', 0)
        """
        if input_number < 0 or input_number > 1000 or not isinstance(input_number, int):
            raise ValueError(f'Only integers, n, within range, 0 <= n <= 1000 are supported.')
        basic_lookup = NUMBER_MAP.get(input_number)
        if basic_lookup:
            numeral = basic_lookup
            remainder = 0
        else:
            multiple = get_multiple(input_number=input_number, multiples=NUMBER_MAP.keys())
            count = input_number // multiple
            remainder = input_number % multiple
            numeral = NUMBER_MAP[multiple] * count
        return numeral, remainder
    
    
    def thousand_lookup(input_number: int, overline_code: str = '\u0305') -> (str, int):
        """
        Returns highest roman numeral possible, that is a multiple of or a thousand that of which can be looked up from
        number map and the remainder (int).
    
        >>> thousand_lookup(3000)
        ('MMM', 0)
        >>> thousand_lookup(300001, overline_code='^')
        ('C^C^C^', 1)
        >>> thousand_lookup(30000002, overline_code='^')
        ('X^^X^^X^^', 2)
        """
        if input_number <= 1000 or not isinstance(input_number, int):
            raise ValueError(f'Only integers, n, within range, n > 1000 are supported.')
        num, k, remainder = get_thousand_count(input_number=input_number)
        numeral = get_roman(input_number=num, overline_code=overline_code)
        numeral = add_overlines(base_numeral=numeral, num_overlines=k, overline_code=overline_code)
    
        # Assume:
        # 4000 -> MV^, https://en.wikipedia.org/wiki/4000_(number)
        # 6000 -> V^M, see https://en.wikipedia.org/wiki/6000_(number)
        # 9000 -> MX^, see https://en.wikipedia.org/wiki/9000_(number)
        numeral = numeral.replace(NUMBER_MAP[1] + overline_code, NUMBER_MAP[1000])
        return numeral, remainder
    
    
    def get_thousand_count(input_number: int) -> (int, int, int):
        """
        Returns three integers defining the number, number of thousands and remainder
    
        >>> get_thousand_count(999)
        (999, 0, 0)
        >>> get_thousand_count(1001)
        (1, 1, 1)
        >>> get_thousand_count(2000002)
        (2, 2, 2)
        """
        num = input_number
        k = 0
        while num >= 1000:
            k += 1
            num //= 1000
        remainder = input_number - (num * 1000 ** k)
        return num, k, remainder
    
    
    def get_multiple(input_number: int, multiples: iter) -> int:
        """
        Given an input number(int) and a list of numbers, finds the number in list closest (rounded down) to input number
    
        >>> get_multiple(45, [1, 2, 3])
        3
        >>> get_multiple(45, [1, 2, 3, 44, 45, 46])
        45
        >>> get_multiple(45, [1, 4, 5, 9, 10, 40, 50, 90])
        40
        """
        options = sorted(list(multiples) + [input_number])
        return options[options.index(input_number) - int(input_number not in multiples)]
    
    
    def add_overlines(base_numeral: str, num_overlines: int = 1, overline_code: str = '\u0305') -> str:
        """
        Adds overlines to input base numeral (string) and returns the result.
    
        >>> add_overlines(base_numeral='II', num_overlines=1, overline_code='^')
        'I^I^'
        >>> add_overlines(base_numeral='I^I^', num_overlines=1, overline_code='^')
        'I^^I^^'
        >>> add_overlines(base_numeral='II', num_overlines=2, overline_code='^')
        'I^^I^^'
        """
        return ''.join([char + overline_code*num_overlines if char.isalnum() else char for char in base_numeral])
    
    
    def gen_number_map() -> dict:
        """
        Returns base number mapping including combinations like 4 -> IV and 9 -> IX, etc.
        """
        mapping = {
            1000: 'M',
            500: 'D',
            100: 'C',
            50: 'L',
            10: 'X',
            5: 'V',
            1: 'I',
            0: 'N'
        }
        for exponent in range(3):
            for num in (4, 9,):
                power = 10 ** exponent
                mapping[num * power] = mapping[1 * power] + mapping[(num + 1) * power]
        return mapping
    
    
    NUMBER_MAP = gen_number_map()
    
    
    if __name__ == '__main__':
        import doctest
        doctest.testmod(verbose=True, raise_on_error=True)
        # Optional extra tests
        # doctest.testfile('test_romanize.txt', verbose=True)
    

    Here are some extra tests in case useful. Save the following as test_romanize.txt in the same directory as the romanize.py:

    The ``romanize`` module
    =======================
    
    
    The ``get_roman`` function
    --------------------------
    
    Import statement:
    
        >>> from romanize import get_roman
    
    Tests:
    
        >>> get_roman(0)
        'N'
        >>> get_roman(6)
        'VI'
        >>> get_roman(11)
        'XI'
        >>> get_roman(345)
        'CCCXLV'
        >>> get_roman(989)
        'CMLXXXIX'
        >>> get_roman(989000000, overline_code='^')
        'C^^M^^L^^X^^X^^X^^M^X^^'
        >>> get_roman(1000)
        'M'
        >>> get_roman(1001)
        'MI'
        >>> get_roman(2000)
        'MM'
        >>> get_roman(2001)
        'MMI'
        >>> get_roman(900)
        'CM'
        >>> get_roman(4000, overline_code='^')
        'MV^'
        >>> get_roman(6000, overline_code='^')
        'V^M'
        >>> get_roman(9000, overline_code='^')
        'MX^'
        >>> get_roman(6001, overline_code='^')
        'V^MI'
        >>> get_roman(9013, overline_code='^')
        'MX^XIII'
        >>> get_roman(70000000000, overline_code='^')
        'L^^^X^^^X^^^'
        >>> get_roman(9000013, overline_code='^')
        'M^X^^XIII'
        >>> get_roman(989888003, overline_code='^')
        'C^^M^^L^^X^^X^^X^^M^X^^D^C^C^C^L^X^X^X^V^MMMIII'
    
    
    The ``get_thousand_count`` function
    --------------------------
    
    Import statement:
    
        >>> from romanize import get_thousand_count
    
    Tests:
    
        >>> get_thousand_count(13)
        (13, 0, 0)
        >>> get_thousand_count(6013)
        (6, 1, 13)
        >>> get_thousand_count(60013)
        (60, 1, 13)
        >>> get_thousand_count(600013)
        (600, 1, 13)
        >>> get_thousand_count(6000013)
        (6, 2, 13)
        >>> get_thousand_count(999000000000000000000000000999)
        (999, 9, 999)
        >>> get_thousand_count(2005)
        (2, 1, 5)
        >>> get_thousand_count(2147483647)
        (2, 3, 147483647)
    
    
    The ``core_lookup`` function
    --------------------------
    
    Import statement:
    
        >>> from romanize import core_lookup
    
    Tests:
    
        >>> core_lookup(2)
        ('II', 0)
        >>> core_lookup(6)
        ('V', 1)
        >>> core_lookup(7)
        ('V', 2)
        >>> core_lookup(19)
        ('X', 9)
        >>> core_lookup(900)
        ('CM', 0)
        >>> core_lookup(999)
        ('CM', 99)
        >>> core_lookup(1000)
        ('M', 0)
        >>> core_lookup(1000.2)
        Traceback (most recent call last):
        ValueError: Only integers, n, within range, 0 <= n <= 1000 are supported.
        >>> core_lookup(10001)
        Traceback (most recent call last):
        ValueError: Only integers, n, within range, 0 <= n <= 1000 are supported.
        >>> core_lookup(-1)
        Traceback (most recent call last):
        ValueError: Only integers, n, within range, 0 <= n <= 1000 are supported.
    
    
    The ``gen_number_map`` function
    --------------------------
    
    Import statement:
    
        >>> from romanize import gen_number_map
    
    Tests:
    
        >>> gen_number_map()
        {1000: 'M', 500: 'D', 100: 'C', 50: 'L', 10: 'X', 5: 'V', 1: 'I', 0: 'N', 4: 'IV', 9: 'IX', 40: 'XL', 90: 'XC', 400: 'CD', 900: 'CM'}
    
    
    The ``get_multiple`` function
    --------------------------
    
    Import statement:
    
        >>> from romanize import get_multiple
        >>> multiples = [0, 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000]
    
    Tests:
    
        >>> get_multiple(0, multiples)
        0
        >>> get_multiple(1, multiples)
        1
        >>> get_multiple(2, multiples)
        1
        >>> get_multiple(3, multiples)
        1
        >>> get_multiple(4, multiples)
        4
        >>> get_multiple(5, multiples)
        5
        >>> get_multiple(6, multiples)
        5
        >>> get_multiple(9, multiples)
        9
        >>> get_multiple(13, multiples)
        10
        >>> get_multiple(401, multiples)
        400
        >>> get_multiple(399, multiples)
        100
        >>> get_multiple(100, multiples)
        100
        >>> get_multiple(99, multiples)
        90
    
    
    The ``add_overlines`` function
    --------------------------
    
    Import statement:
    
        >>> from romanize import add_overlines
    
    Tests:
    
        >>> add_overlines('AB')
        'A\u0305B\u0305'
        >>> add_overlines('A\u0305B\u0305')
        'A\u0305\u0305B\u0305\u0305'
    
        >>> add_overlines('AB', num_overlines=3, overline_code='^')
        'A^^^B^^^'
        >>> add_overlines('A^B^', num_overlines=1, overline_code='^')
        'A^^B^^'
    
        >>> add_overlines('AB', num_overlines=3, overline_code='\u0305')
        'A\u0305\u0305\u0305B\u0305\u0305\u0305'
        >>> add_overlines('A\u0305B\u0305', num_overlines=1, overline_code='\u0305')
        'A\u0305\u0305B\u0305\u0305'
    
        >>> add_overlines('A^B', num_overlines=3, overline_code='^')
        'A^^^^B^^^'
    
        >>> add_overlines('A^B', num_overlines=0, overline_code='^')
        'A^B'
    

提交回复
热议问题