Roman Numerals Sample C# Code

Code:

    public class RomanNumbers
    {
        private readonly IDictionary<string, int> m_RomanNumbers;

        public RomanNumbers()
        {
            m_RomanNumbers = new Dictionary<string, int> 
            {{"I", 1},{"V", 5},{"X", 10},{"L", 50},{"C", 100},{"D", 500},{"M", 1000}};
        }

        public int FromRomanToDecimal(string romanNumber)
        {
            if (string.IsNullOrEmpty(romanNumber)) throw new ArgumentNullException("romanNumber");
            int decimalResult = 0;

            IList<int> decimalNumbers = new List<int>();
            if (!TryConvertToDecimalDigits(romanNumber, decimalNumbers)) return 0;

            for (int i = 0; i < decimalNumbers.Count; i++)
            {
                if (i + 1 >= decimalNumbers.Count) return decimalResult + decimalNumbers[i];

                // substraction
                if (decimalNumbers[i] < decimalNumbers[i + 1])
                {
                    decimalNumbers[i] = decimalNumbers[i + 1] - decimalNumbers[i];
                    decimalResult = decimalResult + decimalNumbers[i];

                    decimalNumbers.RemoveAt(i + 1);
                }
                // addition
                else
                {
                    decimalResult = decimalResult + decimalNumbers[i];
                }
            }

            return decimalResult;
        }

        private bool TryConvertToDecimalDigits(string romanNumber, ICollection<int> numbers)
        {
            foreach (char c in romanNumber)
            {
                string number = c.ToString();
                if (!m_RomanNumbers.ContainsKey(number)) return false;
                int decimalValue = m_RomanNumbers[number];

                if (numbers.Count(n => n == decimalValue) > 3) return false;
                if (!ValidateCombinations(decimalValue, numbers)) return false;

                numbers.Add(decimalValue);
            }

            return !WrongOrder(numbers);
        }

        private bool ValidateCombinations(int number, ICollection<int> decimalNumbers)
        {
            if (decimalNumbers.Count == 0) return true;
            int last = decimalNumbers.Last();

            if ((number == 5 || number == 50 || number == 500) && last == number) return false;
            if (last >= number) return true;

            if (decimalNumbers.Count(n => n == last) > 1) return false;

            if ((number == 5 || number == 10) && last == 1) return true;
            if ((number == 50 || number == 100) && last == 10) return true;
            if ((number == 500 || number == 1000) && last == 100) return true;

            return false;
        }

        private bool WrongOrder(ICollection<int> decimalNumbers)
        {
            if (decimalNumbers.Count == 0) return true;

            int prevNumber = 0;
            int prePrevNumber = 0;

            foreach (int number in decimalNumbers)
            {
                if (prevNumber != 0 && prePrevNumber != 0)
                {
                    if (prePrevNumber < number) return true;
                    if (prePrevNumber == number && prevNumber > number) return true;
                }

                prePrevNumber = prevNumber;
                prevNumber = number;
            }

            return false;
        }
    }

Test:

    [TestClass]
    public class RomanNumbersTest
    {
        [TestMethod]
        public void TestSimpleConvertion()
        {
            // arrange
            RomanNumbers target = new RomanNumbers();
            IDictionary<string, int> testCases = new Dictionary<string, int>();
            testCases.Add("I", 1);
            testCases.Add("II", 2);
            testCases.Add("IIII", 4);
            testCases.Add("V", 5);
            testCases.Add("VIIII", 9);
            testCases.Add("X", 10);
            testCases.Add("XI", 11);
            testCases.Add("XV", 15);
            testCases.Add("XX", 20);
            testCases.Add("XXV", 25);
            testCases.Add("XXX", 30);
            testCases.Add("XXXXIIII", 44);

            // act
            foreach (KeyValuePair<string, int> testCase in testCases)
            {
                int result = target.FromRomanToDecimal(testCase.Key);

                // assert
                Assert.AreEqual(testCase.Value, result);
            }
        }

        [TestMethod]
        public void TestSubtractionConvertion()
        {
            // arrange
            RomanNumbers target = new RomanNumbers();
            IDictionary<string, int> testCases = new Dictionary<string, int>();
            testCases.Add("IV", 4);
            testCases.Add("IX", 9);
            testCases.Add("XL", 40);
            testCases.Add("XLIV", 44);
            testCases.Add("CMIX", 909);
            testCases.Add("MMCDXIX", 2419);

            // act
            foreach (KeyValuePair<string, int> testCase in testCases)
            {
                int result = target.FromRomanToDecimal(testCase.Key);

                // assert
                Assert.AreEqual(testCase.Value, result);
            }
        }

        [TestMethod]
        public void TestSyntaxErrors()
        {
            // arrange
            RomanNumbers target = new RomanNumbers();
            IDictionary<string, int> testCases = new Dictionary<string, int>();
            testCases.Add("abcdef", 0);
            testCases.Add("IIIII", 0);
            testCases.Add("VV", 0);
            testCases.Add("XXXXXX", 0);
            testCases.Add("IC", 0);
            testCases.Add("IIX", 0);
            testCases.Add("IXCM", 0);
            testCases.Add("XVXV", 0);
            testCases.Add("XIXI", 0);
            testCases.Add("CMM", 0);
            testCases.Add("MMCDIXX", 0);

            // act
            foreach (KeyValuePair<string, int> testCase in testCases)
            {
                int result = target.FromRomanToDecimal(testCase.Key);
                // assert
                Assert.AreEqual(testCase.Value, result);
            }
        }
    }