Saturday, March 15, 2008

Roman Numerals, Part 2

Last week we converted integers into Roman numerals, and I promised this week we would write a function to do the opposite. Part of the difficulty in working with Roman numerals is the subtractive notation. The number four can be written as IV or IIII; writing it as IV is the subtractive notation. When I precedes V, it means one less than five; if you reverse the two (VI), it means one more than five.


To make our life easier, we want to expand the subtractive notation so that each numeral is equal to its value. While we're at it, we'll also write a function to compress Roman numerals back in subtractive notation.


ASP

  1. function roman_expand(ByVal roman)
  2.     roman = replace(roman, "CM", "DCCCC")
  3.     roman = replace(roman, "CD", "CCCC")
  4.     roman = replace(roman, "XC", "LXXXX")
  5.     roman = replace(roman, "XL", "XXXX")
  6.     roman = replace(roman, "IX", "VIIII")
  7.     roman = replace(roman, "IV", "IIII")
  8.     roman_expand = roman
  9. end function
  10. function roman_compress(ByVal roman)
  11.     roman = replace(roman, "DCCCC", "CM")
  12.     roman = replace(roman, "CCCC", "CD")
  13.     roman = replace(roman, "LXXXX", "XC")
  14.     roman = replace(roman, "XXXX", "XL")
  15.     roman = replace(roman, "VIIII", "IX")
  16.     roman = replace(roman, "IIII", "IV")
  17.     roman_compress = roman
  18. end function

PHP

  1. function roman_expand($roman)
  2. {
  3.     $roman = str_replace("CM", "DCCCC", $roman);
  4.     $roman = str_replace("CD", "CCCC", $roman);
  5.     $roman = str_replace("XC", "LXXXX", $roman);
  6.     $roman = str_replace("XL", "XXXX", $roman);
  7.     $roman = str_replace("IX", "VIIII", $roman);
  8.     $roman = str_replace("IV", "IIII", $roman);
  9.     return $roman;
  10. }
  11. function roman_compress($roman)
  12. {
  13.     $roman = str_replace("DCCCC", "CM", $roman);
  14.     $roman = str_replace("CCCC", "CD", $roman);
  15.     $roman = str_replace("LXXXX", "XC", $roman);
  16.     $roman = str_replace("XXXX", "XL", $roman);
  17.     $roman = str_replace("VIIII", "IX", $roman);
  18.     $roman = str_replace("IIII", "IV", $roman);
  19.     return $roman;
  20. }

These expand and compress functions would be essential if you wanted to write some functions to actually perform arithmetic on Roman numerals, and although on paper Roman addition is fairly straightforward, an implementation of it in code performs more poorly than simply converting the Roman numeral back into an integer and using the built-in math operators.


The following ASP function requires the InstrCount function from my previous post.


ASP

  1. function arabic(ByVal roman)
  2.     Dim result
  3.     result = 0
  4.     ' Remove subtractive notation.
  5.     roman = roman_expand(roman)
  6.     ' Calculate for each numeral.
  7.     result = result + InstrCount(roman, "M") * 1000
  8.     result = result + InstrCount(roman, "D") * 500
  9.     result = result + InstrCount(roman, "C") * 100
  10.     result = result + InstrCount(roman, "L") * 50
  11.     result = result + InstrCount(roman, "X") * 10
  12.     result = result + InstrCount(roman, "V") * 5
  13.     result = result + InstrCount(roman, "I")
  14. end function

PHP

  1. function arabic($roman)
  2. {
  3.     $result = 0;
  4.     // Remove subtractive notation.
  5.     $roman = roman_expand($roman);
  6.     // Calculate for each numeral.
  7.     $result += substr_count($roman, 'M') * 1000;
  8.     $result += substr_count($roman, 'D') * 500;
  9.     $result += substr_count($roman, 'C') * 100;
  10.     $result += substr_count($roman, 'L') * 50;
  11.     $result += substr_count($roman, 'X') * 10;
  12.     $result += substr_count($roman, 'V') * 5;
  13.     $result += substr_count($roman, 'I');
  14.     return $result;
  15. }

Although I don't have code for you to actually add two Roman numerals together, I will tell you how to do it. For this example, we will add the numerals XIV and VII. First we must remove the subtractive notation, so XIV becomes XIIII and VII stays as it is. On paper we would then concatenate the two together and sort them.


XIIII + VII = XIIIIVII = XVIIIIII


We already have a concatenation operator, but we would need to write a custom sorting function for Roman numerals. If we're going to go that much trouble, it might be better to just build a new string by counting the number of each numeral in the two strings we are given. This would allow us to handle the situation above where we now have six I's. Our next step is to combine similar numerals that are equal to the next larger numeral.


XVIIIIII = XVVI = XXI


In code, it would be best to start with the smallest numeral, I, and work our way towards the largest numeral.


Subtraction of Roman numerals involves expanding and cancelling until there is only one number left.


XIV - VI = XIIII - VI = XIII - V = VVIII - V = VIII


I leave it up to you to research how to perform multiplication and division of Roman numerals, and whether or not you want to write functions for Roman arithmetic. Such functions would not be useful in production code, but writing them would be a good exercise, and it would be interesting to compare the runtimes with conversion back to Arabic followed by a conversion to Roman again.


Next weekend is Easter, so we'll be writing code to calculate when Easter and its related holidays (Good Friday, Pentecost, etc.) occur for any given year.

No comments: