Saturday, July 05, 2008

Who in their right mind would use Roman numerals for anything?

And, who in their right mind would bother to write code to deal with it, since it's already been done about 5 million times? Well, me for one. OK, yes, I am a turbodork. I can no longer deny it. Here's the code:

/**
 * Represent Roman numerals between 1 and 4999 inclusive. 
 */
public class Roman implements Comparable {
 private int value;
 private String roman;

 private final static String[][] digits = {
  {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"},
  {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"},
  {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"},
  {"", "M", "MM", "MMM", "MMMM"}
 };

 private final static Pattern regex = Pattern.compile("M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})");


 public Roman(int n) {
  value = n;
  roman = intToRoman(n);
 }

 public Roman(String string) {
  if (isRoman(string)) {
   value = romanToInt(string);
   roman = string;
  }
  else
   throw new RuntimeException("\""+string+"\" is not a valid roman numeral.");
 }

 public String toString() {
  return roman;
 }

 public int toInt() {
  return value;
 }

 /**
  * Test whether the input string is a valid roman numeral (between 1 and 4999).
  */
 public static boolean isRoman(String s) {
  return s!=null && !"".equals(s) && regex.matcher(s).matches();
 }

 /**
  * Convert an integer between 1 and 4999 (inclusive) to a Roman numeral.
  */
 public static String intToRoman(int n) {
  if (n>=5000 || n <= 0)
   throw new RuntimeException("Roman equivalents exist only for numbers between 1 and 4999.");
  StringBuilder sb = new StringBuilder(16);
  int place = 0;
  while (n>0) {
   int qoutient = n/10;
   int remainder = n - (qoutient * 10);
   sb.insert(0, digits[place][remainder]);
   place++;
   n = qoutient;
  }
  return sb.toString();
 }

 /**
  * Convert a roman numeral to an integer. This method isn't strict about
  * the rules of what makes a valid roman numeral. In particular, it will
  * happily convert IIIIIIIIII to 10 or IL to 49 even though neither of
  * these are valid roman numerals. To check whether a String is a valid
  * roman numeral use isRoman().
  */
 public static int romanToInt(String roman) {
  int value = 0;
  int prev = 1000;

  for (int i=0; i prev) {
    value -= (prev << 1);
   }
   value += v;
   prev = v;
  }

  return value;
 }

 /**
  * @return return the integer value of a single roman character.
  * @throws RuntimeException if the character isn't a roman numeral.
  */
 private static int charToValue(char c) {
  if (c=='I') return 1;
  if (c=='V') return 5;
  if (c=='X') return 10;
  if (c=='L') return 50;
  if (c=='C') return 100;
  if (c=='D') return 500;
  if (c=='M') return 1000;
  throw new RuntimeException("Illegal character in roman numeral: " + c);
 }

 public int compareTo(Roman other) {
  return this.value - other.value;
 }

 @Override
 public int hashCode() {
  return value;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  Roman other = (Roman) obj;
  return this.value == other.value;
 }
}