Roman numerals to integers

落花浮王杯 提交于 2019-11-28 08:40:44

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;
}

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;
}

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");
            }



        }

    }

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;
}

Borrowed a lot from System.Linq on this one. String implements IEnumerable<char>, so I figured that was appropriate since we are treating it as an enumerable object anyways. Tested it against a bunch of random numbers, including 1, 3, 4, 8, 83, 99, 404, 555, 846, 927, 1999, 2420.

    public static IDictionary<char, int> CharValues 
    { 
        get 
        { 
            return new Dictionary<char, int>
            {{'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}};
        } 
    }

    public static int RomanNumeralToInteger(IEnumerable<char> romanNumerals)
    {
        int retVal = 0;

        //go backwards
        for (int i = romanNumerals.Count() - 1; i >= 0; i--)
        {
            //get current character
            char c = romanNumerals.ElementAt(i);

            //error checking
            if (!CharValues.ContainsKey(c)) throw new InvalidRomanNumeralCharacterException(c);

            //determine if we are adding or subtracting
            bool op = romanNumerals.Skip(i).Any(rn => CharValues[rn] > CharValues[c]);

            //then do so
            retVal = op ? retVal - CharValues[c] : retVal + CharValues[c];
        }

        return retVal;
    }

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;
}

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

I will suggest a simplest method for this by using array in .net : comments are given in C# section for explanation

VB.net

Public Class Form1
    Dim indx() As Integer = {1, 2, 3, 4, 5, 10, 50, 100, 500, 1000}
    Dim row() As String = {"I", "II", "III", "IV", "V", "X", "L", "C", "D", "M"}
    Dim limit As Integer = 9
    Dim output As String = ""
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim num As Integer
        output = ""
        num = CInt(txt1.Text)
        While num > 0
            num = find(num)
        End While
        txt2.Text = output
    End Sub
    Public Function find(ByVal Num As Integer) As Integer
        Dim i As Integer = 0
        While indx(i) <= Num
            i += 1
        End While
        If i <> 0 Then
            limit = i - 1
        Else
            limit = 0
        End If
        output = output & row(limit)
        Num = Num - indx(limit)
        Return Num
    End Function
End Class

C#

using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class Form1
{
    int[] indx = {
        1,
        2,
        3,
        4,
        5,
        10,
        50,
        100,
        500,
        1000
        // initialize array of integers 
    };
    string[] row = {
        "I",
        "II",
        "III",
        "IV",
        "V",
        "X",
        "L",
        "C",
        "D",
        "M"
        //Carasponding roman letters in for the numbers in the array
    };
        // integer to indicate the position index for link two arrays 
    int limit = 9;
        //string to store output
    string output = "";
    private void Button1_Click(System.Object sender, System.EventArgs e)
    {
        int num = 0;
        // stores the input 
        output = "";
        // clear output before processing
        num = Convert.ToInt32(txt1.Text);
        // get integer value from the textbox
        //Loop until the value became 0
        while (num > 0) {
            num = find(num);
            //call function for processing
        }
        txt2.Text = output;
        // display the output in text2
    }
    public int find(int Num)
    {
        int i = 0;
        // loop variable initialized with 0
        //Loop until the indx(i).value greater than or equal to num
        while (indx(i) <= Num) {
            i += 1;
        }
        // detemine the value of limit depends on the itetration
        if (i != 0) {
            limit = i - 1;
        } else {
            limit = 0;
        }
        output = output + row(limit);
        //row(limit) is appended with the output
        Num = Num - indx(limit);
        // calculate next num value
        return Num;
        //return num value for next itetration 
    }
}

I refer from this blog. You could just reverse the roman numeral , then all the thing would be more easier compare to make the comparison.
public static int pairConversion(int dec, int lastNum, int lastDec) { if (lastNum > dec) return lastDec - dec; else return lastDec + dec; }

    public static int ConvertRomanNumtoInt(string strRomanValue)
    {
        var dec = 0;
        var lastNum = 0;
        foreach (var c in strRomanValue.Reverse())
        {
            switch (c)
            {
                case 'I':
                    dec = pairConversion(1, lastNum, dec);
                    lastNum = 1;
                    break;
                case 'V':
                    dec=pairConversion(5,lastNum, dec);
                    lastNum = 5;
                    break;
                case 'X':
                    dec = pairConversion(10, lastNum, dec);
                    lastNum = 10;
                    break;
                case 'L':
                    dec = pairConversion(50, lastNum, dec);
                    lastNum = 50;
                    break;
                case 'C':
                    dec = pairConversion(100, lastNum, dec);
                    lastNum = 100;
                    break;
                case 'D':
                    dec = pairConversion(500, lastNum, dec);
                    lastNum = 500;
                    break;
                case 'M':
                    dec = pairConversion(1000, lastNum, dec);
                    lastNum = 1000;
                    break;
            }
        }
        return dec;

    }

This one uses a stack:

    public int RomanToInt(string s)
    {
        var dict = new Dictionary<char, int>();
        dict['I'] = 1;
        dict['V'] = 5;
        dict['X'] = 10;
        dict['L'] = 50;
        dict['C'] = 100;
        dict['D'] = 500;
        dict['M'] = 1000;
        Stack<char> st = new Stack<char>();
        foreach (char ch in s.ToCharArray())
            st.Push(ch);

        int result = 0;
        while (st.Count > 0)
        {
            var c1=st.Pop();
            var ch1 = dict[c1];

            if (st.Count > 0)
            {
                var c2 = st.Peek();
                var ch2 = dict[c2];
                if (ch2 < ch1)
                {
                    result += (ch1 - ch2);
                    st.Pop();
                }
                else
                {
                    result += ch1;
                }
            }
            else
            {
                result += ch1;
            }
        }
        return result;
    }
Jack Griffin

I wrote this just using arrays.
I omit the testing code here, but it looks it works properly.

public static class RomanNumber {
        static string[] units = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
        static string[] tens = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
        static string[] hundreds = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
        static string[] thousands = { "", "M", "MM", "MMM" };

        static public bool IsRomanNumber(string source) {
            try {
                return RomanNumberToInt(source) > 0;
            }
            catch {
                return false;
            }
        }

        /// <summary>
        /// Parses a string containing a roman number.
        /// </summary>
        /// <param name="source">source string</param>
        /// <returns>The integer value of the parsed roman numeral</returns>
        /// <remarks>
        /// Throws an exception on invalid source.
        /// Throws an exception if source is not a valid roman number.
        /// Supports roman numbers from "I" to "MMMCMXCIX" ( 1 to 3999 )
        /// NOTE : "IMMM" is not valid</remarks>
        public static int RomanNumberToInt(string source) {
            if (String.IsNullOrWhiteSpace(source)) {
                throw new ArgumentNullException();
            }

            int total = 0;
            string buffer = source;

            // parse the last four characters in the string
            // each time we check the buffer against a number array,
            // starting from units up to thousands
            // we quit as soon as there are no remaing characters to parse

            total += RipOff(buffer, units, out buffer);

            if (buffer != null) {
                total += (RipOff(buffer, tens, out buffer)) * 10;
            }

            if (buffer != null) {
                total += (RipOff(buffer, hundreds, out buffer)) * 100;
            }

            if (buffer != null) {
                total += (RipOff(buffer, thousands, out buffer)) * 1000;
            }

            // after parsing for thousands, if there is any character left, this is not a valid roman number
            if (buffer != null) {
                throw new ArgumentException(String.Format("{0} is not a valid roman number", buffer));
            }
            return total;
        }


        /// <summary>
        /// Given a string, takes the four characters on the right,
        /// search an element in the numbers array and returns the remaing characters.
        /// </summary>
        /// <param name="source">source string to parse</param>
        /// <param name="numbers">array of roman numerals</param>
        /// <param name="left">remaining characters on the left</param>
        /// <returns>If it finds a roman numeral returns its integer value; otherwise returns zero</returns>
        public static int RipOff(string source, string[] numbers, out string left) {
            int result = 0;

            string buffer = null;

            // we take the last four characters : this is the length of the longest numeral in our arrays
            // ("VIII", "LXXX", "DCCC")
            // or all if source length is 4 or less
            if (source.Length > 4) {
                buffer = source.Substring(source.Length - 4);
                left = source.Substring(0, source.Length - 4);
            }
            else {
                buffer = source;
                left = null;
            }

            // see if buffer exists in the numbers array 
            // if it does not, skip the first character and try again
            // until buffer contains only one character
            // append the skipped character to the left arguments
            while (!numbers.Contains(buffer)) {
                if (buffer.Length == 1) {
                    left = source; // failed
                    break;
                }
                else {
                    left += buffer.Substring(0, 1);
                    buffer = buffer.Substring(1);
                }
            }

            if (buffer.Length > 0) {
                if (numbers.Contains(buffer)) {
                    result = Array.IndexOf(numbers, buffer);
                }
            }

            return result;
        }
    }
}

EDIT
Forget about it !
Just look at BrunoLM solution here.
It's simple and elegant.
The only caveat is that it does not check the source.

This is my solution:

    /// <summary>
    /// Converts a Roman number string into a Arabic number
    /// </summary>
    /// <param name="romanNumber">the Roman number string</param>
    /// <returns>the Arabic number (0 if the given string is not convertible to a Roman number)</returns>
    public static int ToArabicNumber(string romanNumber)
    {
        string[] replaceRom = { "CM", "CD", "XC", "XL", "IX", "IV" };
        string[] replaceNum = { "DCCCC", "CCCC", "LXXXX", "XXXX", "VIIII", "IIII" };
        string[] roman = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
        int[] arabic = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 };
        return Enumerable.Range(0, replaceRom.Length)
            .Aggregate
            (
                romanNumber,
                (agg, cur) => agg.Replace(replaceRom[cur], replaceNum[cur]),
                agg => agg.ToArray()
            )
            .Aggregate
            (
                0,
                (agg, cur) =>
                {
                    int idx = Array.IndexOf(roman, cur.ToString());
                    return idx < 0 ? 0 : agg + arabic[idx];
                },
                agg => agg
            );
    }

    /// <summary>
    /// Converts a Arabic number into a Roman number string
    /// </summary>
    /// <param name="arabicNumber">the Arabic number</param>
    /// <returns>the Roman number string</returns>
    public static string ToRomanNumber(int arabicNumber)
    {
        string[] roman = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
        int[] arabic = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 };
        return Enumerable.Range(0, arabic.Length)
            .Aggregate
            (
                Tuple.Create(arabicNumber, string.Empty),
                (agg, cur) =>
                {
                    int remainder = agg.Item1 % arabic[cur];
                    string concat = agg.Item2 + string.Concat(Enumerable.Range(0, agg.Item1 / arabic[cur]).Select(num => roman[cur]));
                    return Tuple.Create(remainder, concat);
                },
                agg => agg.Item2
            );
    }

Here's the Explanation how the methods work:

ToArabicNumber

First aggregation step is to Replace the Roman Number special cases (e.g.: IV -> IIII). Second Aggregate step simply sums up the equivalent Arabic number of the Roman letter (e.g. V -> 5)

ToRomanNumber:

I start the aggregation with the given Arabic number. For each step the number will be divided by the equivalent number of the Roman letter. The remainder of this division is then the input for the next step. The division Result will be translated to the Equivalent Roman Number character which will be appended to the result string.

Solution with fulfilling the "subtractive notation" semantics checks

None of the current solutions completely fulfills the entire set of rules for the "subtractive notation". "IIII" -> is not possible. Each of the solutions results a 4. Also the strings: "CCCC", "VV", "IC", "IM" are invalid.

A good online-converter to check the semantics is https://www.romannumerals.org/converter So, if you really want doing a completely semantics-check, it's much more complex.

My approach was to first write the unit tests with the semantic checks. Then to write the code. Then to reduce the loops with some linq expressions.

Maybe there is a smarter solution, but I think the following code fullfills the rules to convert a roman numerals string.

After the code section, there is a section with my unit tests.

public class RomanNumerals
{

    private List<Tuple<char, ushort, char?[]>> _validNumerals = new List<Tuple<char, ushort, char?[]>>()
    {
        new Tuple<char, ushort, char?[]>('I', 1, new char? [] {'V', 'X'}),
        new Tuple<char, ushort, char?[]>('V', 5, null),
        new Tuple<char, ushort, char?[]>('X', 10, new char?[] {'L', 'C'}),
        new Tuple<char, ushort, char?[]>('L', 50, null),
        new Tuple<char, ushort, char?[]>('C', 100, new char? [] {'D', 'M'}),
        new Tuple<char, ushort, char?[]>('D', 500, null),
        new Tuple<char, ushort, char?[]>('M', 1000, new char? [] {null, null})
    };


    public int TranslateRomanNumeral(string input)
    {
        var inputList = input?.ToUpper().ToList();

        if (inputList == null || inputList.Any(x => _validNumerals.Select(t => t.Item1).Contains(x) == false))
        {
            throw new ArgumentException();
        }

        char? valForSubtraction = null;
        int result = 0;
        bool noAdding = false;
        int equalSum = 0;
        for (int i = 0; i < inputList.Count; i++)
        {
            var currentNumeral = _validNumerals.FirstOrDefault(s => s.Item1 == inputList[i]);
            var nextNumeral = i < inputList.Count - 1 ? _validNumerals.FirstOrDefault(s => s.Item1 == inputList[i + 1]) : null;
            bool currentIsDecimalPower = currentNumeral?.Item3?.Any() ?? false;

            if (nextNumeral != null)
            {
                // Syntax and Semantics checks
                if ((currentNumeral.Item2 < nextNumeral.Item2) && (currentIsDecimalPower == false || currentNumeral.Item3.Any(s => s == nextNumeral.Item1) == false) ||
                    (currentNumeral.Item2 == nextNumeral.Item2) && (currentIsDecimalPower == false || nextNumeral.Item1 == valForSubtraction) ||
                    (currentIsDecimalPower && result > 0 &&  ((nextNumeral.Item2 -currentNumeral.Item2) > result )) ||
                    (currentNumeral.Item2 > nextNumeral.Item2) && (nextNumeral.Item1 == valForSubtraction)

                    )
                {
                    throw new ArgumentException();
                }

                if (currentNumeral.Item2 == nextNumeral.Item2)
                {
                    equalSum += equalSum == 0 ? currentNumeral.Item2 + nextNumeral.Item2 : nextNumeral.Item2;
                    int? smallest = null;
                    var list = _validNumerals.Where(p => _validNumerals.FirstOrDefault(s => s.Item1 == currentNumeral.Item1).Item3.Any(s2 => s2 != null && s2 == p.Item1)).ToList();
                    if (list.Any())
                    {
                        smallest = list.Select(s3 => s3.Item2).ToList().Min();
                    }

                    // Another Semantics check
                    if (currentNumeral.Item3 != null && equalSum >= (smallest - currentNumeral.Item2))
                    {
                        throw new ArgumentException();
                    }

                    result += noAdding ? 0 : currentNumeral.Item2 + nextNumeral.Item2;
                    noAdding = !noAdding;
                    valForSubtraction = null;
                }
                else
                if (currentNumeral.Item2 < nextNumeral.Item2)
                {
                    equalSum = 0;
                    result += nextNumeral.Item2 - currentNumeral.Item2;
                    valForSubtraction = currentNumeral.Item1;
                    noAdding = true;
                }
                else 
                if (currentNumeral.Item2 > nextNumeral.Item2)
                {
                    equalSum = 0;
                    result += noAdding ? 0 : currentNumeral.Item2;
                    noAdding = false;

                    valForSubtraction = null;
                }
            }
            else
            {
                result += noAdding ? 0 : currentNumeral.Item2;
            }
        }
        return result;
    }
}

Here are the UNIT tests

    [TestFixture]
public class RomanNumeralsTests
{
    [Test]
    public void TranslateRomanNumeral_WhenArgumentIsNull_RaiseArgumentNullException()
    {
        var romanNumerals = new RomanNumerals();

        Assert.Throws<ArgumentException>(() => romanNumerals.TranslateRomanNumeral(null));
    }

    [TestCase("A")]
    [TestCase("-")]
    [TestCase("BXA")]
    [TestCase("MMXK")]
    public void TranslateRomanNumeral_WhenInvalidNumeralSyntax_RaiseException(string input)
    {
        var romanNumerals = new RomanNumerals();

        Assert.Throws<ArgumentException>(() => romanNumerals.TranslateRomanNumeral(input));
    }

    [TestCase("IIII")]
    [TestCase("CCCC")]
    [TestCase("VV")]
    [TestCase("IC")]
    [TestCase("IM")]
    [TestCase("XM")]
    [TestCase("IL")]
    [TestCase("MCDXCXI")]
    [TestCase("MCDDXC")]
    public void TranslateRomanNumeral_WhenInvalidNumeralSemantics_RaiseException(string input)
    {
        var romanNumerals = new RomanNumerals();

        Assert.Throws<ArgumentException>(() => romanNumerals.TranslateRomanNumeral(input));
    }


    [TestCase("I", 1)]
    [TestCase("II", 2)]
    [TestCase("III", 3)]
    [TestCase("IV", 4)]
    [TestCase("XLII", 42)]
    [TestCase("MMXIII", 2013)]
    [TestCase("MXI", 1011)]
    [TestCase("MCDXCIX", 1499)]
    [TestCase("MMXXII", 2022)]
    [TestCase("V", 5)]
    [TestCase("VI", 6)]
    [TestCase("CX", 110)]
    [TestCase("CCCLXXV", 375)]
    [TestCase("MD", 1500)]
    [TestCase("MDLXXV", 1575)]
    [TestCase("MDCL", 1650)]
    [TestCase("MDCCXXV", 1725)]
    [TestCase("MDCCC", 1800)]
    [TestCase("MDCCCLXXV", 1875)]
    [TestCase("MCML", 1950)]
    [TestCase("MMXXV", 2025)]
    [TestCase("MMC", 2100)]
    [TestCase("MMCLXXV", 2175)]
    [TestCase("MMCCL", 2250)]
    [TestCase("MMCCCXXV", 2325)]
    [TestCase("MMCD", 2400)]
    [TestCase("MMCDLXXV", 2475)]
    [TestCase("MMDL", 2550)]
    [TestCase("MMMMMMMM", 8000)]
    [TestCase("MMMMMMMMIV", 8004)]
    public void TranslateRomanNumeral_WhenValidNumeral_Translate(string input, int output)
    {
        var romanNumerals = new RomanNumerals();

        var result = romanNumerals.TranslateRomanNumeral(input);

        Assert.That(result.Equals(output));
    }
}
private static HashMap<Character, Integer> romanMap = new HashMap<>() {{
    put('I', 1); put('V', 5); put('X', 10); put('L', 50); 
    put('C', 100); put('D', 500); put('M', 1000);
}};

private static int convertRomanToInt(String romanNumeral) {
    int total = 0;
    romanNumeral = romanNumeral.toUpperCase();

    //add every Roman numeral
    for(int i = 0; i < romanNumeral.length(); i++) {
        total += romanMap.get(romanNumeral.charAt(i));
    }

    //remove the Roman numerals that are followed 
    //directly by a larger Roman numeral
    for(int i = 0; i < romanNumeral.length()-1; i++) {
        if(romanMap.get(romanNumeral.charAt(i)) 
           < romanMap.get(romanNumeral.charAt(i+1))) {
           total -= 2* romanMap.get(romanNumeral.charAt(i));
        }
    }
    return total;
}

//note that the topmost solution does not solve this Roman numeral
//but mine does
//also note that this solution is a preference of simplicity over complexity
public static void main(String[] args) {
    String rn = "CcLXxiV"; //274
    System.out.println("Convert " + rn + " to " + convertRomanToInt(rn));
}
will simmons
public static int ConvertRomanNumtoInt(string strRomanValue)
{
    Dictionary RomanNumbers = new Dictionary
    {
        {"M", 1000},
        {"CM", 900},
        {"D", 500},
        {"CD", 400},
        {"C", 100},
        {"XC", 90},
        {"L", 50},
        {"XL", 40},
        {"X", 10},
        {"IX", 9},
        {"V", 5},
        {"IV", 4},
        {"I", 1}
    };
    int retVal = 0;
    foreach (KeyValuePair pair in RomanNumbers)
    {
        while (strRomanValue.IndexOf(pair.Key.ToString()) == 0)
        {
            retVal += int.Parse(pair.Value.ToString());
            strRomanValue = strRomanValue.Substring(pair.Key.ToString().Length);
        }
    }
    return retVal;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!