Roman numeral to integer ======================== .. contents:: :local: .. include:: roman_numeral_introduction.rst Exercise 1 ---------- Approach ^^^^^^^^ Get each symbol from left to right by iterating over the roman numeral and test if the next symbol has a higher value the current one. If that's the case substract its value, otherwise add its value to the sum of all symbol. .. interactive_code_block:: :caption: Complete the function that converts a roman numeral to an integer def roman_numeral_to_integer(roman_numeral): """Convert a roman numeral to an integer.""" # Create a dict with the roman symbols and their integer values roman_symbols = { 'M':1000, 'D':500, 'C': 100, 'L': 50, 'X': 10, 'V': 5, 'I': 1 } # Create a variable to be able to add the values of the symbols result = 0 # Iterate over all symbols in the roman numeral from left to right for #.......... # Get the value of the current symbol #.......... # Use the index to test if there is still a symbol follwing the current one if #.......... # Get the value of the next symbol #.......... #.......... # If the value of the current symbol is lower than the one of the next... if #.......... # ...than substract the value to the result... #.......... else: # ...otherwise add the value to the result #.......... else: # There is no next symbol left: add the value of the current symbol #.......... return result # Create a dict with test values test_values = {'CXXIII': 123, 'CCCXXI':321, 'CDXLIV':444, 'CMXCIX': 999, 'MMXXI': 2021} # Print a table with test values and the result of the function print(' Test values | Result') print('------------------+-------') for roman_numeral, result in test_values.items(): print('{:>8} | {:6} | {:6}'.format(roman_numeral, result, roman_numeral_to_integer(roman_numeral))) Know how ^^^^^^^^ Using a dict as lookup table """""""""""""""""""""""""""" Though the data type of a ``dict`` is a *mapping* and not a *sequence* like ``str``, ``tuple`` and ``list`` they can be seen as a sequence of key / value pairs to be used as a `lookup table `__. The most noticable difference between a mapping and a sequence is that a ``dict``'s value can *only* be accessed by its key and not by its index. .. interactive_code_block:: :caption: Using a dict as lookup table = look up a value by its key roman_symbols = { 'M':1000, 'D':500, 'C': 100, 'L': 50, 'X': 10, 'V': 5, 'I': 1 } # Get the value for the key 'C' print(roman_symbols['C']) Iterating over a string """"""""""""""""""""""" ``for ... in ... :`` loops are the simplest way to iterate over any sequence with a fixed step size. .. interactive_code_block:: :caption: Version 1 - using a 'for ... in ... :' loop roman_numeral = 'CXXIII' for symbol in roman_numeral: print(symbol) To test if the last item has been reached it is also required to know the current index. Therfore the built-in function ``enumerate()`` has to be used. .. interactive_code_block:: :caption: Using 'enumerate()' to get the index within a 'for ... in ... :' loop roman_numeral = 'CXXIII' for index, letter in enumerate(roman_numeral): print(index, letter) Slicing a string """""""""""""""" Parts of strings can be 'sliced' (= cut out) by using square brackets and indices. Be aware that start indices start counting at 0 and not 1. Up to three numbers can be supplied ``sequence[start:stop:step]`` with the last two being optional. .. interactive_code_block:: :caption: Slicing out a sequence of two letters of a string roman_numeral = 'CXXIII' print(roman_numeral[2:4]) # Note that the second index after the colon':' is out of range here... print(roman_numeral[5:7]) # ...but an 'IndexError' gets only thrown while trying to access a single element print(roman_numeral[7]) Go to :ref:`solution `. Exercise 2 ---------- Approach ^^^^^^^^ An alternative approach is to treat the 6 possible double-letter combinations * CM = 900, CD = 400, XC = 90, XL = 40, IX = 9, IV = 4 as an extension to the 7 single-letter symbols by and test for double-letter symbols first and if that fails use the single-letter symbols. .. interactive_code_block:: :caption: Complete the function that converts a roman numeral to an integer def roman_numeral_to_integer(roman_numeral): """Convert a roman numeral to an integer.""" # Create a dict with the roman symbols and their integer values roman_symbols = { 'M':1000, 'CM':900, 'D':500, 'CD':400, 'C': 100, 'XC': 90, 'L': 50, 'XL': 40, 'X': 10, 'IX': 9, 'V': 5, 'IV': 4, 'I': 1 } # Create a variable to be able to add the values of the symbols result = 0 # Create a variable to be used as index index = 0 # Iterate over the symbols of the roman numeral until the end has been reached while #.......... # Test if there is not the last symbol if #.......... # Test if this is a double-letter symbol if #.......... # Add the value for the double-letter symbol... #.......... # ...and increment the index by 2 #.......... # Otherwise else: # Add the value for the single-letter symbol... #.......... # Increment the index by 1 #.......... # Otherwise else: # Add the value for the single-letter symbol... #.......... # Increment the index by 1 #.......... return result # Create a dict with test values test_values = {'CXXIII': 123, 'CCCXXI':321, 'CDXLIV':444, 'CMXCIX': 999, 'MMXXI': 2021} # Print a table with test values and the result of the function print(' Test values | Result') print('------------------+-------') for roman_numeral, result in test_values.items(): print('{:>8} | {:6} | {:6}'.format(roman_numeral, result, roman_numeral_to_integer( roman_numeral))) Know how ^^^^^^^^ Iterating over a string with varying step sizes """"""""""""""""""""""""""""""""""""""""""""""" Depending if a two-letter combination has been found or not the step size has to be changed. Here a ``while ...:`` loop might be the easier way though it can also be done with a ``for ... in ... :`` loop (in a not so obvious way). .. interactive_code_block:: :caption: Using a 'while ... :' loop to skip every second letter roman_numeral = 'CXXIII' index = 0 while index < len(roman_numeral): symbol = roman_numeral[index] print(symbol) index += 2 .. interactive_code_block:: :caption: Using a 'for ... in ... :' loop to skip every second letter roman_numeral = 'CXXIII' iterator = iter(roman_numeral) for symbol in iterator: print(symbol) next(iterator) .. interactive_code_block:: :caption: Using a 'for ... in ... :' loop to skip every second letter in combination with 'enumerate()' roman_numeral = 'CXXIII' iterator = iter(enumerate(roman_numeral)) for index, symbol in iterator: print(index, symbol) next(iterator) Go to :ref:`solution `.