The very first problem with your approach is that you have to use greedy algorithm: if you have, say CDL
input you should treat it as CD + L == 450
, not as C + D + L == 650
. Another (may be minor) issue is that you allow incorrect input like DD
or IIV
, XM
etc.
An accurate implementation with the syntax check, can be written with a help of FSA (Finite State Automata). E.g. these inputs are syntactically incorrect:
MIM, LL, XLX, CDC, XXXX, CCCXCX, CCCXL, VL, VX
Something like this:
private static Dictionary<char, int> s_Romans = new Dictionary<char, int>() {
{'M', 1000}, {'D', 500}, {'C', 100}, {'L', 50}, {'X', 10}, {'V', 5}, {'I', 1},
};
private static int RomanToArabic(string value) {
if (null == value)
throw new ArgumentNullException("value");
else if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Empty or WS only value is not allowed.", "value");
int v;
int[] values = value
.Select(c => s_Romans.TryGetValue(c, out v) ? v : -1)
.ToArray();
int result = 0;
int current = 1000;
int count = 0;
for (int i = 0; i < values.Length; ++i) {
v = values[i];
if (v < 0)
throw new FormatException($"Invalid symbol {value[i]} at {i + 1}");
else if (v > current)
throw new FormatException($"Invalid symbol {value[i]}");
else if (current == v) {
count += 1;
if (count > 1 && (v == 5 || v == 50 || v == 500))
throw new FormatException($"Invalid symbol {value[i]} at {i + 1}");
else if (count > 3 && current != 1000)
throw new FormatException($"Invalid symbol {value[i]} at {i + 1}");
}
else {
count = 1;
current = v;
}
if (i < value.Length - 1)
if (v == 1 || v == 10 || v == 100)
if (v * 5 == values[i + 1] || v * 10 == values[i + 1]) {
v = values[i + 1] - v;
count = 3;
i += 1;
}
result += v;
}
return result;
}
Some Tests:
// 4000
Console.Write(RomanToArabic("MMMM"));
// 1444
Console.Write(RomanToArabic("MCDXLIV"));
// 1009
Console.Write(RomanToArabic("MIX"));
// 1
Console.Write(RomanToArabic("I"));
More tests:
Converting integers to roman numerals
// Mosè Bottacini's code see the link above
public static string ToRoman(int number) {
if ((number < 0) || (number > 3999))
throw new ArgumentOutOfRangeException("insert value between 1 and 3999");
if (number < 1) return string.Empty;
if (number >= 1000) return "M" + ToRoman(number - 1000);
if (number >= 900) return "CM" + ToRoman(number - 900);
if (number >= 500) return "D" + ToRoman(number - 500);
if (number >= 400) return "CD" + ToRoman(number - 400);
if (number >= 100) return "C" + ToRoman(number - 100);
if (number >= 90) return "XC" + ToRoman(number - 90);
if (number >= 50) return "L" + ToRoman(number - 50);
if (number >= 40) return "XL" + ToRoman(number - 40);
if (number >= 10) return "X" + ToRoman(number - 10);
if (number >= 9) return "IX" + ToRoman(number - 9);
if (number >= 5) return "V" + ToRoman(number - 5);
if (number >= 4) return "IV" + ToRoman(number - 4);
if (number >= 1) return "I" + ToRoman(number - 1);
throw new ArgumentOutOfRangeException("something bad happened");
}
...
var failed = Enumerable.Range(1, 3000)
.Select(i => new {
arabic = i,
roman = ToRoman(i)
})
.Where(item => item.arabic != RomanToArabic(item.roman))
.Select(item => $"{item.roman} expected: {item.arabic} actual: {RomanToArabic(item.roman)}");
// No line should be printed out
Console.Write(string.Join(Environment.NewLine, failed));