Roman Numerals Helper in Java


The challenge

Create a RomanNumerals class that can convert a roman numeral to and from an integer value. It should follow the API demonstrated in the examples below. Multiple roman numeral values will be tested for each helper method.

Modern Roman numerals are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. In Roman numerals 1990 is rendered: 1000=M, 900=CM, 90=XC; resulting in MCMXC. 2008 is written as 2000=MM, 8=VIII; or MMVIII. 1666 uses each Roman symbol in descending order: MDCLXVI.

Examples

RomanNumerals.toRoman(1000) // should return 'M'
RomanNumerals.fromRoman("M") // should return 1000

Help

SymbolValue
I1
V5
X10
L50
C100
D500
M1000

The solution in Java code

Option 1:

public class RomanNumerals {
    private static final String[] ROMAN_NUMBERS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
    private static final int[] ARABIC_NUMBERS = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};

    public static String toRoman(int n) {
        int remainingValue = n;
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < ARABIC_NUMBERS.length; i++) {
            while (remainingValue >= ARABIC_NUMBERS[i]) {
                remainingValue -= ARABIC_NUMBERS[i];
                result.append(ROMAN_NUMBERS[i]);
            }
        }

        return result.toString();
    }

    public static int fromRoman(String romanNumeral) {
        String remainingValue = romanNumeral;
        int result = 0;

        for(int i = 0; i<ROMAN_NUMBERS.length; i++) {
            while(remainingValue.startsWith(ROMAN_NUMBERS[i])) {
                remainingValue = remainingValue.substring(ROMAN_NUMBERS[i].length(), remainingValue.length());
                result += ARABIC_NUMBERS[i];
            }
        }
        return result;
    }
}

Option 2:

public class RomanNumerals {
 
  public static String toRoman(int n) {
      String s = "", r[] = {"I", "V", "X", "L", "C", "D", "M"};
      int j = 0;
      while (n > 0) {
          switch (n%10) {
              case 1: s = r[j]                          + s; break;
              case 2: s = r[j]   + r[j]                 + s; break;
              case 3: s = r[j]   + r[j]   + r[j]        + s; break;
              case 4: s = r[j]   + r[j+1]               + s; break;
              case 5: s = r[j+1]                        + s; break;
              case 6: s = r[j+1] + r[j]                 + s; break;
              case 7: s = r[j+1] + r[j]   + r[j]        + s; break;
              case 8: s = r[j+1] + r[j]   + r[j] + r[j] + s; break;
              case 9: s = r[j]   + r[j+2]               + s; break;
        }
        j = j + 2;
        n = n/10;
      }
      return s;
  }
  
  public static int fromRoman(String s) {
      String r[] = {"IV", "IX", "XL", "XC", "CD", "CM"};
      for (String i : r) {
          switch (i) {
              case "IV": s = s.replace(i, "IIII"); break;
              case "IX": s = s.replace(i, "VIIII"); break;
              case "XL": s = s.replace(i, "XXXX"); break;
              case "XC": s = s.replace(i, "LXXXX"); break;
              case "CD": s = s.replace(i, "CCCC"); break;
              case "CM": s = s.replace(i, "DCCCC"); break;
          }
      }
      char c[] = s.toCharArray();
      int n = 0;
      for (int i : c) {
          switch (i) {
              case 'I': n += 1; break;
              case 'V': n += 5; break;
              case 'X': n += 10; break;
              case 'L': n += 50; break;
              case 'C': n += 100; break;
              case 'D': n += 500; break;
              case 'M': n += 1000; break;
          }
      }
      return n;
  }
}

Option 3:

public class RomanNumerals {
  private static int[] numArray = {1,4,5,9,10,40,50,90,100,400,500,900,1000};
  private static String[] numeralArray = {"I","IV","V","IX","X","XL","L","XC","C","CD","D","CM","M"};
 
  public static String toRoman(int n) {
    StringBuilder output = new StringBuilder();
    int i = numArray.length - 1;
    while (n > 0) {
      int numOption = numArray[i];
      while (n / numOption >= 1) {
        output.append(numeralArray[i]);
        n -= numOption;
      }
      i--;
    }
    return output.toString();
  }
  
  public static int fromRoman(String romanNumeral) {
    int num = 0;
    int strIndex = 0;
    int numArrIndex = numeralArray.length - 1;
    while (strIndex < romanNumeral.length()) {
      String currentNumeral = numeralArray[numArrIndex];
      if (romanNumeral.charAt(strIndex) == currentNumeral.charAt(0)) {
        if (currentNumeral.length() > 1) {
          if (strIndex + 1 < romanNumeral.length() && romanNumeral.charAt(strIndex + 1) == currentNumeral.charAt(1)) {
            num += numArray[numArrIndex];
            strIndex += 2;
          } else {
            numArrIndex--;
          }
        } else {
          num += numArray[numArrIndex];
          strIndex++;
        }
      } else {
        numArrIndex--;
      }
    }
    return num;
  }
}

Test cases to validate our solution

import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class RomanNumeralsTest {

    @Test
    public void testToRoman() throws Exception {
        assertThat("1 converts to 'I'", RomanNumerals.toRoman(1), is("I"));
        assertThat("2 converts to 'II'", RomanNumerals.toRoman(2), is("II"));
    }

    @Test
    public void testFromRoman() throws Exception {
        assertThat("'I' converts to 1", RomanNumerals.fromRoman("I"), is(1));
        assertThat("'II' converts to 2", RomanNumerals.fromRoman("II"), is(2));
    }
}