Converting Roman Numerals To Decimal

2019-01-01 15:54发布

I have managed to get my code to convert most Roman numerals to its appropriate decimal value. But it doesn't work for some exceptional cases. Example : XCIX = 99 but my code prints 109.

Here is my code.

public static int romanConvert(String roman)
{
    int decimal = 0;

    String romanNumeral = roman.toUpperCase();
    for(int x = 0;x<romanNumeral.length();x++)
    {
        char convertToDecimal = roman.charAt(x);

        switch (convertToDecimal)
        {
        case 'M':
            decimal += 1000;
            break;

        case 'D':
            decimal += 500;
            break;

        case 'C':
            decimal += 100;
            break;

        case 'L':
            decimal += 50;
            break;

        case 'X':
            decimal += 10;
            break;

        case 'V':
            decimal += 5;
            break;

        case 'I':
            decimal += 1;
            break;
        }
    }
    if (romanNumeral.contains("IV"))
    {
        decimal-=2;
    }
    if (romanNumeral.contains("IX"))
    {
        decimal-=2;
    }
    if (romanNumeral.contains("XL"))
    {
        decimal-=10;
    }
    if (romanNumeral.contains("XC"))
    {
        decimal-=10;
    }
    if (romanNumeral.contains("CD"))
    {
        decimal-=100;
    }
    if (romanNumeral.contains("CM"))
    {
        decimal-=100;
    }
    return decimal;
}

标签: java
26条回答
路过你的时光
2楼-- · 2019-01-01 16:18
public class RomanNumeral {

private final Map<Integer, String> arabicToRoman = new LinkedHashMap<Integer, String>();
private final Map<String, Integer> romanToArabic = new LinkedHashMap<String, Integer>();

public RomanNumeral() {
    arabicToRoman.put(10, "X");
    arabicToRoman.put(9, "IX");
    arabicToRoman.put(5, "V");
    arabicToRoman.put(4, "IV");
    arabicToRoman.put(1, "I");
    romanToArabic.put("X", 10);
    romanToArabic.put("V", 5);
    romanToArabic.put("I", 1);
}

public String convertToRomanNumeral(int number) {
    String result = "";

    for (Integer i : arabicToRoman.keySet()) {
        while (number >= i) {
            result += arabicToRoman.get(i);
            number -= i;
        }
    }

    return result;
}

public String convertToArabicNumber(String romanNumeral) {
    int result = 0;

    int top = 0;
    for (int i = romanNumeral.length() - 1; i >= 0; i--) {
        char current = romanNumeral.charAt(i);
        int value = romanToArabic.get("" + current);
        if (value < top) {
            result -= value;
        } else {
            result += value;
            top = value;
        }
    }

    return "" + result;
}

}
查看更多
不再属于我。
3楼-- · 2019-01-01 16:19
public static int convertFromRoman(String romanNumeral)
{
    Character[] rnChars = { 'M',  'D', 'C',   'L',  'X',  'V', 'I' };
    int[] rnVals = {  1000,  500, 100,  50, 10, 5, 1 };
    HashMap<Character, Integer> valueLookup = new HashMap<Character, Integer>();
    for (int i = 0; i < rnChars.length;i++)
        valueLookup.put(rnChars[i], rnVals[i]);
    int retVal = 0;
    for (int i = 0; i < romanNumeral.length();i++)
    {
        int addVal = valueLookup.get(romanNumeral.charAt(i));
        retVal += i < romanNumeral.length()-1 && 
                  addVal < valueLookup.get(romanNumeral.charAt(i+1))?
                  -addVal:addVal;
    }
    return retVal;
}
查看更多
情到深处是孤独
4楼-- · 2019-01-01 16:20

You can check following code. This code should work on all cases. Also it checks null or empty input and faulty input (Let's say you tried with ABXI)

import java.util.HashMap;
import org.apache.commons.lang3.StringUtils;

public class RomanToDecimal {
  private HashMap<Character, Integer> map;

  public RomanToDecimal() {
    map = new HashMap<>();
    map.put('I', 1);
    map.put('V', 5);
    map.put('X', 10);
    map.put('L', 50);
    map.put('C', 100);
    map.put('D', 500);
    map.put('M', 1000);
  }

  private int getRomanNumeralValue(char ch) {
    if (map.containsKey(ch)) {
      return map.get(ch);
    }
    else {
      throw new RuntimeException("Roman numeral string contains invalid characters " + ch);
    }
  }

  public int convertRomanToDecimal(final String pRomanNumeral) {
    if (StringUtils.isBlank(pRomanNumeral)) {
      throw new RuntimeException("Roman numeral string is either null or empty");
    }
    else {
      int index = pRomanNumeral.length() - 1;
      int result = getRomanNumeralValue(pRomanNumeral.charAt(index));

      for (int i = index - 1; i >= 0; i--) {
        if (getRomanNumeralValue(pRomanNumeral.charAt(i)) >= getRomanNumeralValue(pRomanNumeral.charAt(i + 1))) {
          result = result + getRomanNumeralValue(pRomanNumeral.charAt(i));
        }
        else {
          result = result - getRomanNumeralValue(pRomanNumeral.charAt(i));
        }
      }
      return result;
    }
  }
public static void main(String... args){
      System.out.println(new RomanToDecimal().convertRomanToDecimal("XCIX"));
  }

}
查看更多
牵手、夕阳
5楼-- · 2019-01-01 16:20

Full version with error checking and test all valid values in both directions (and some invalid cases).

RomanNumeral.java

import java.util.ArrayList;
import java.util.TreeMap;

/**
 * Convert to and from a roman numeral string
 */
public class RomanNumeral {

    // used for converting from arabic number
    final static TreeMap<Integer, String> mapArabic = new TreeMap<Integer, String>();
    // used for converting from roman numeral
    final static ArrayList<RomanDigit> mapRoman = new ArrayList<RomanDigit>();
    final static int MAX_ARABIC = 3999;

    static {
        mapArabic.put(1000, "M");
        mapArabic.put(900, "CM");
        mapArabic.put(500, "D");
        mapArabic.put(400, "CD");
        mapArabic.put(100, "C");
        mapArabic.put(90, "XC");
        mapArabic.put(50, "L");
        mapArabic.put(40, "XL");
        mapArabic.put(10, "X");
        mapArabic.put(9, "IX");
        mapArabic.put(5, "V");
        mapArabic.put(4, "IV");
        mapArabic.put(1, "I");

        mapRoman.add(new RomanDigit("M", 1000, 3, 1000));
        mapRoman.add(new RomanDigit("CM", 900, 1, 90));
        mapRoman.add(new RomanDigit("D", 500, 1, 100));
        mapRoman.add(new RomanDigit("CD", 400, 1, 90));
        mapRoman.add(new RomanDigit("C", 100, 3, 100));
        mapRoman.add(new RomanDigit("XC", 90, 1, 9));
        mapRoman.add(new RomanDigit("L", 50, 1, 10));
        mapRoman.add(new RomanDigit("XL", 40, 1, 9));
        mapRoman.add(new RomanDigit("X", 10, 3, 10));
        mapRoman.add(new RomanDigit("IX", 9, 1, 0));
        mapRoman.add(new RomanDigit("V", 5, 1, 1));
        mapRoman.add(new RomanDigit("IV", 4, 1, 0));
        mapRoman.add(new RomanDigit("I", 1, 3, 1));
    }

    static final class RomanDigit {
        public final String numeral;
        public final int value;
        public final int maxConsecutive;
        public final int maxNextValue;

        public RomanDigit(String numeral, int value, int maxConsecutive, int maxNextValue) {
            this.numeral = numeral;
            this.value = value;
            this.maxConsecutive = maxConsecutive;
            this.maxNextValue = maxNextValue;
        }
    }

    /**
     * Convert an arabic integer value into a roman numeral string
     *
     * @param n The arabic integer value
     * @return The roman numeral string
     */
    public final static String toRoman(int n) {
        if (n < 1 || n > MAX_ARABIC) {
            throw new NumberFormatException(String.format("Invalid arabic value: %d, must be > 0 and < %d", n, MAX_ARABIC));
        }

        int leDigit = mapArabic.floorKey(n);
        //System.out.println("\t*** floor of " + n + " is " + leDigit);
        if (n == leDigit) {
            return mapArabic.get(leDigit);
        }
        return mapArabic.get(leDigit) + toRoman(n - leDigit);
    }

    /**
     * Convert a roman numeral string into an arabic integer value
     * @param s The roman numeral string
     * @return The arabic integer value
     */
    public final static int toInt(String s) throws NumberFormatException {
        if (s == null || s.length() == 0) {
            throw new NumberFormatException("Invalid roman numeral: a non-empty and non-null value must be given");
        }

        int i = 0;
        int iconsecutive = 0; // number of consecutive same digits
        int pos = 0;
        int sum = 0;
        RomanDigit prevDigit = null;

        while (pos < s.length()) {

            RomanDigit d = mapRoman.get(i);
            if (!s.startsWith(mapRoman.get(i).numeral, pos)) {
                i++;
                // this is the only place we advance which digit we are checking,
                // so if it exhausts the digits, then there is clearly a digit sequencing error or invalid digit
                if (i == mapRoman.size()) {
                    throw new NumberFormatException(
                            String.format("Invalid roman numeral at pos %d: invalid sequence '%s' following digit '%s'",
                                    pos, s.substring(pos), prevDigit != null ? prevDigit.numeral : ""));
                }
                iconsecutive = 0;
                continue;
            }

            // we now have the match for the next roman numeral digit to check
            iconsecutive++;
            if (iconsecutive > d.maxConsecutive) {
                throw new NumberFormatException(
                        String.format("Invalid roman numeral at pos %d: more than %d consecutive occurences of digit '%s'",
                                pos, d.maxConsecutive, d.numeral));
            }

            // invalid to encounter a higher digit sequence than the previous digit expects to have follow it
            // (any digit is valid to start a roman numeral - i.e. when prevDigit == null)
            if (prevDigit != null && prevDigit.maxNextValue < d.value) {
                throw new NumberFormatException(
                        String.format("Invalid roman numeral at pos %d: '%s' cannot follow '%s'",
                                pos, d.numeral, prevDigit.numeral));
            }

            // good to sum
            sum += d.value;
            if (sum > MAX_ARABIC) {
                throw new NumberFormatException(
                        String.format("Invalid roman numeral at pos %d: adding '%s' exceeds the max value of %d",
                                pos, d.numeral, MAX_ARABIC));
            }
            pos += d.numeral.length();
            prevDigit = d;
        }

        return sum;
    }
}

Main.java

public class Main {
    public static void main(String[] args) {

        System.out.println("TEST arabic integer => roman numeral string");
        for (int i = 0; i<= 4000; i++) {
            String s;
            try {
                s = RomanNumeral.toRoman(i);
            }
            catch(NumberFormatException ex) {
                s = ex.getMessage();
            }
            System.out.println(i + "\t =\t " + s);
        }

        System.out.println("TEST roman numeral string => arabic integer");
        for (int i = 0; i<= 4000; i++) {
            String s;
            String msg;
            try {
                s = RomanNumeral.toRoman(i);
                int n = testToInt(s);
                assert(i == n); // ensure it is reflexively converting
            }
            catch (NumberFormatException ex) {
                System.out.println(ex.getMessage() + "\t =\t toInt() skipped");
            }
        }

        testToInt("MMMM");
        testToInt("XCX");
        testToInt("CDC");
        testToInt("IVI");
        testToInt("XXC");
        testToInt("CCD");
        testToInt("MDD");
        testToInt("DD");
        testToInt("CLL");
        testToInt("LL");
        testToInt("IIX");
        testToInt("IVX");
        testToInt("IIXX");
        testToInt("XCIX");
        testToInt("XIWE");

        // Check validity via a regexp for laughs
        String s = "IX";
        System.out.println(s + " validity is " + s.matches("M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})"));
    }

    private final static int testToInt(String s) {
        String msg;
        int n=0;
        try {
            n = RomanNumeral.toInt(s);
            msg = Integer.toString(n);
        }
        catch(NullPointerException | NumberFormatException ex) {
            msg = ex.getMessage();
        }

        System.out.println(s + "\t =\t " + msg);
        return n;
    }
}

Output

TEST arabic integer => roman numeral string
0    =   Invalid arabic value: 0, must be > 0 and < 3999
1    =   I
2    =   II
3    =   III
4    =   IV
5    =   V
6    =   VI
7    =   VII
8    =   VIII
9    =   IX
10   =   X
... [snip] ...
3988     =   MMMCMLXXXVIII
3989     =   MMMCMLXXXIX
3990     =   MMMCMXC
3991     =   MMMCMXCI
3992     =   MMMCMXCII
3993     =   MMMCMXCIII
3994     =   MMMCMXCIV
3995     =   MMMCMXCV
3996     =   MMMCMXCVI
3997     =   MMMCMXCVII
3998     =   MMMCMXCVIII
3999     =   MMMCMXCIX
4000     =   Invalid arabic value: 4000, must be > 0 and < 3999
TEST roman numeral string => arabic integer
Invalid arabic value: 0, must be > 0 and < 3999  =   toInt() skipped
I    =   1
II   =   2
III  =   3
IV   =   4
V    =   5
VI   =   6
VII  =   7
VIII     =   8
IX   =   9
X    =   10
... [snip] ...
MMMCMLXXXVIII    =   3988
MMMCMLXXXIX  =   3989
MMMCMXC  =   3990
MMMCMXCI     =   3991
MMMCMXCII    =   3992
MMMCMXCIII   =   3993
MMMCMXCIV    =   3994
MMMCMXCV     =   3995
MMMCMXCVI    =   3996
MMMCMXCVII   =   3997
MMMCMXCVIII  =   3998
MMMCMXCIX    =   3999
Invalid arabic value: 4000, must be > 0 and < 3999   =   toInt() skipped
MMMM     =   Invalid roman numeral at pos 3: more than 3 consecutive occurences of digit 'M'
XCX  =   Invalid roman numeral at pos 2: 'X' cannot follow 'XC'
CDC  =   Invalid roman numeral at pos 2: 'C' cannot follow 'CD'
IVI  =   Invalid roman numeral at pos 2: 'I' cannot follow 'IV'
XXC  =   Invalid roman numeral at pos 2: invalid sequence 'C' following digit 'X'
CCD  =   Invalid roman numeral at pos 2: invalid sequence 'D' following digit 'C'
MDD  =   Invalid roman numeral at pos 2: more than 1 consecutive occurences of digit 'D'
DD   =   Invalid roman numeral at pos 1: more than 1 consecutive occurences of digit 'D'
CLL  =   Invalid roman numeral at pos 2: more than 1 consecutive occurences of digit 'L'
LL   =   Invalid roman numeral at pos 1: more than 1 consecutive occurences of digit 'L'
IIX  =   Invalid roman numeral at pos 2: invalid sequence 'X' following digit 'I'
IVX  =   Invalid roman numeral at pos 2: invalid sequence 'X' following digit 'IV'
IIXX     =   Invalid roman numeral at pos 2: invalid sequence 'XX' following digit 'I'
XCIX     =   99
XIWE     =   Invalid roman numeral at pos 2: invalid sequence 'WE' following digit 'I'
IX validity is true
查看更多
深知你不懂我心
6楼-- · 2019-01-01 16:21

assuming the hash looks something like this

Hashtable<Character, Integer> ht = new Hashtable<Character, Integer>();
    ht.put('i',1);
    ht.put('x',10);
    ht.put('c',100);
    ht.put('m',1000);
    ht.put('v',5);
    ht.put('l',50);
    ht.put('d',500);

then the logic gets pretty simple going by digit right to left

public static int rtoi(String num)
{       
    int intNum=0;
    int prev = 0;
    for(int i = num.length()-1; i>=0 ; i--)
    {
            int temp = ht.get(num.charAt(i));
            if(temp < prev)
                intNum-=temp;
            else
                intNum+=temp;
            prev = temp;
    }
    return intNum;
}   
查看更多
残风、尘缘若梦
7楼-- · 2019-01-01 16:21

Following your logic of reducing 2 on IX you should reduce 20 on XC 200 on CM and so on.

查看更多
登录 后发表回答