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