Roman numerals to integers

In data sometimes the same product will be named with a roman numeral while other times it will be a digit.

Example Samsung Galaxy SII verses Samsung Galaxy S2

How can the II be converted to the value 2?


I've noticed some really complicated solutions here but this is a really simple problem. I made a solution that avoided the need to hard code the "exceptions" (IV, IX, XL, etc). I used a for loop to look ahead at the next character in the Roman numeral string to see if the number associated with the numeral should be subtracted or added to the total. For simplicity's sake I'm assuming all input is valid.

private static Dictionary<char, int> RomanMap = new Dictionary<char, int>()
    {
        {'I', 1},
        {'V', 5},
        {'X', 10},
        {'L', 50},
        {'C', 100},
        {'D', 500},
        {'M', 1000}
    };

public static int RomanToInteger(string roman)
{
    int number = 0;
    for (int i = 0; i < roman.Length; i++)
    {
        if (i + 1 < roman.Length && RomanMap[roman[i]] < RomanMap[roman[i + 1]])
        {
            number -= RomanMap[roman[i]];
        }
        else
        {
            number += RomanMap[roman[i]];
        }
    }
    return number;
}

I initially tried using a foreach on the string which I think was a slightly more readable solution but I ended up adding every single number and subtracting it twice later if it turned out to be one of the exceptions, which I didn't like. I'll post it here anyway for posterity.

public static int RomanToInteger(string roman)
{
    int number = 0;
    char previousChar = roman[0];
    foreach(char currentChar in roman)
    {
        number += RomanMap[currentChar];
        if(RomanMap[previousChar] < RomanMap[currentChar])
        {
            number -= RomanMap[previousChar] * 2;
        }
        previousChar = currentChar;
    }
    return number;
}

This is my solution

public int SimplerConverter(string number)
    {
        number = number.ToUpper();
        var result = 0;

        foreach (var letter in number)
        {
            result += ConvertLetterToNumber(letter);
        }

        if (number.Contains("IV")|| number.Contains("IX"))
            result -= 2;

        if (number.Contains("XL")|| number.Contains("XC"))
            result -= 20;

        if (number.Contains("CD")|| number.Contains("CM"))
            result -= 200;


        return result;



    }

    private int ConvertLetterToNumber(char letter)
    {
        switch (letter)
        {
            case 'M':
            {
                return 1000;
            }

            case 'D':
            {
                return 500;
            }

            case 'C':
            {
                return 100;
            }

            case 'L':
            {
                return 50;
            }

            case 'X':
            {
                return 10;
            }

            case 'V':
            {
                return 5;
            }

            case 'I':
            {
                return 1;
            }

            default:
            {
                throw new ArgumentException("Ivalid charakter");
            }



        }

    }

A more simple and readable C# implementation that:

  • maps I to 1, V to 5, X to 10, L to 50, C to 100, D to 500, M to 1000.
  • uses one single foreach loop (foreach used on purpose, with previous value hold).
  • adds the mapped number to the total.
  • subtracts twice the number added before, if I before V or X, X before L or C, C before D or M (not all chars are allowed here!).
  • returns 0 (not used in Roman numerals) on empty string, wrong letter or not allowed char used for subtraction.
  • remark: it's still not totally complete, we didn't check all possible conditions for a valid input string!

Code:

private static Dictionary<char, int> _romanMap = new Dictionary<char, int>
{
   {'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}
};

public static int ConvertRomanToNumber(string text)
{
    int totalValue = 0, prevValue = 0;
    foreach (var c in text)
    {
        if (!_romanMap.ContainsKey(c))
            return 0;
        var crtValue = _romanMap[c];
        totalValue += crtValue;
        if (prevValue != 0 && prevValue < crtValue)
        {
            if (prevValue == 1 && (crtValue == 5 || crtValue == 10)
                || prevValue == 10 && (crtValue == 50 || crtValue == 100)
                || prevValue == 100 && (crtValue == 500 || crtValue == 1000))
                totalValue -= 2 * prevValue;
            else
                return 0;
        }
        prevValue = crtValue;
    }
    return totalValue;
}

I wrote a simple Roman Numeral Converter just now, but it doesn't do a whole lot of error checking, but it seems to work for everything I could throw at it that is properly formatted.

public class RomanNumber
{
    public string Numeral { get; set; }
    public int Value { get; set; }
    public int Hierarchy { get; set; }
}

public List<RomanNumber> RomanNumbers = new List<RomanNumber>
    {
        new RomanNumber {Numeral = "M", Value = 1000, Hierarchy = 4},
        //{"CM", 900},
        new RomanNumber {Numeral = "D", Value = 500, Hierarchy = 4},
        //{"CD", 400},
        new RomanNumber {Numeral = "C", Value = 100, Hierarchy = 3},
        //{"XC", 90},
        new RomanNumber {Numeral = "L", Value = 50, Hierarchy = 3},
        //{"XL", 40},
        new RomanNumber {Numeral = "X", Value = 10, Hierarchy = 2},
        //{"IX", 9},
        new RomanNumber {Numeral = "V", Value = 5, Hierarchy = 2},
        //{"IV", 4},
        new RomanNumber {Numeral = "I", Value = 1, Hierarchy = 1}
    };

/// <summary>
/// Converts the roman numeral to int, assumption roman numeral is properly formatted.
/// </summary>
/// <param name="romanNumeralString">The roman numeral string.</param>
/// <returns></returns>
private int ConvertRomanNumeralToInt(string romanNumeralString)
{
    if (romanNumeralString == null) return int.MinValue;

    var total = 0;
    for (var i = 0; i < romanNumeralString.Length; i++)
    {
        // get current value
        var current = romanNumeralString[i].ToString();
        var curRomanNum = RomanNumbers.First(rn => rn.Numeral.ToUpper() == current.ToUpper());

        // last number just add the value and exit
        if (i + 1 == romanNumeralString.Length)
        {
            total += curRomanNum.Value;
            break;
        } 

        // check for exceptions IV, IX, XL, XC etc
        var next = romanNumeralString[i + 1].ToString();
        var nextRomanNum = RomanNumbers.First(rn => rn.Numeral.ToUpper() == next.ToUpper());

        // exception found
        if (curRomanNum.Hierarchy == (nextRomanNum.Hierarchy - 1))
        {
            total += nextRomanNum.Value - curRomanNum.Value;
            i++;
        }
        else
        {
            total += curRomanNum.Value;
        }
    }


    return total;
}

I landed here searching for a small implementation of a Roman Numerals parser but wasn't satisfied by the provided answers in terms of size and elegance. I leave my final, recursive implementation here, to help others searching a small implementation.


Convert Roman Numerals by Recursion

  • The algorithm is able to non-adjacent numerals as well (f.e. XIIX).
  • This implementation may only work with well-formed (strings matching /[mdclxvi]*/i) roman numerals.
  • The implementation is not optimized for speed.
// returns the value for a roman literal
private static int romanValue(int index)
{
    int basefactor = ((index % 2) * 4 + 1); // either 1 or 5...
    // ...multiplied with the exponentation of 10, if the literal is `x` or higher
    return index > 1 ? (int) (basefactor * System.Math.Pow(10.0, index / 2)) : basefactor;
}

public static int FromRoman(string roman)
{
    roman = roman.ToLower();
    string literals = "mdclxvi";
    int value = 0, index = 0;
    foreach (char literal in literals)
    {
        value = romanValue(literals.Length - literals.IndexOf(literal) - 1);
        index = roman.IndexOf(literal);
        if (index > -1)
            return FromRoman(roman.Substring(index + 1)) + (index > 0 ? value - FromRoman(roman.Substring(0, index)) : value);
    }
    return 0;
}

Try it using this .Netfiddle: https://dotnetfiddle.net/veaNk3

How does it work?

This algorithm calculates the value of a Roman Numeral by taking the highest value from the Roman Numeral and adding/subtracting recursively the value of the remaining left/right parts of the literal.

ii X iiv # Pick the greatest value in the literal `iixiiv` (symbolized by uppercase)

Then recursively reevaluate and subtract the lefthand-side and add the righthand-side:

(iiv) + x - (ii) # Subtract the lefthand-side, add the righthand-side
(V - (ii)) + x - ((I) + i) # Pick the greatest values, again
(v - ((I) + i)) + x - ((i) + i) # Pick the greatest value of the last numeral compound

Finally the numerals are substituted by their integer values:

(5 - ((1) + 1)) + 10 - ((1) + 1)
(5 - (2)) + 10 - (2)
3 + 10 - 2
= 11