I\'m trying to write a function that converts numbers to roman numerals. This is my code so far; however, it only works with numbers that are less than 400. Is there a quick
In 1 line, not very efficient but works:
public string RomanNumeralFrom(int number)
{
return
new string('I', number)
.Replace(new string('I', 1000), "M")
.Replace(new string('I', 900), "CM")
.Replace(new string('I', 500), "D")
.Replace(new string('I', 400), "CD")
.Replace(new string('I', 100), "C")
.Replace(new string('I', 90), "XC")
.Replace(new string('I', 50), "L")
.Replace(new string('I', 40), "XL")
.Replace(new string('I', 10), "X")
.Replace(new string('I', 9), "IX")
.Replace(new string('I', 5), "V")
.Replace(new string('I', 4), "IV");
}
This version doesn't "cheat" as others: it generates internally the "base" table with all the "base" "composable" numbers. For lazyness I'm using Tuple
s, instead of creating specialized classes. If you don't have C# 4.0, you can replace Tuple<>
with KeyValuePair<>
, Item1
with Key
and Item2
with Value
.
static Tuple<IList<Tuple<string, int>>, int> GenerateBaseNumbers()
{
const string letters = "IVXLCDM";
var tuples = new List<Tuple<string, int>>();
Tuple<string, int> subtractor = null;
int num = 1;
int maxNumber = 0;
for (int i = 0; i < letters.Length; i++)
{
string currentLetter = letters[i].ToString();
if (subtractor != null)
{
tuples.Add(Tuple.Create(subtractor.Item1 + currentLetter, num - subtractor.Item2));
}
tuples.Add(Tuple.Create(currentLetter, num));
bool isEven = i % 2 == 0;
if (isEven)
{
subtractor = tuples[tuples.Count - 1];
}
maxNumber += isEven ? num * 3 : num;
num *= isEven ? 5 : 2;
}
return Tuple.Create((IList<Tuple<string, int>>)new ReadOnlyCollection<Tuple<string, int>>(tuples), maxNumber);
}
static readonly Tuple<IList<Tuple<string, int>>, int> RomanBaseNumbers = GenerateBaseNumbers();
static string FromNumberToRoman(int num)
{
if (num <= 0 || num > RomanBaseNumbers.Item2)
{
throw new ArgumentOutOfRangeException();
}
StringBuilder sb = new StringBuilder();
int i = RomanBaseNumbers.Item1.Count - 1;
while (i >= 0)
{
var current = RomanBaseNumbers.Item1[i];
if (num >= current.Item2)
{
sb.Append(current.Item1);
num -= current.Item2;
}
else
{
i--;
}
}
return sb.ToString();
}
static void Main(string[] args)
{
for (int i = 1; i <= RomanBaseNumbers.Item2; i++)
{
var calc = FromNumberToRoman(i);
Console.WriteLine("{1}", i, calc);
}
}
I've created this class that does decimal <=> roman
public static class Roman
{
public static readonly Dictionary<char, int> RomanNumberDictionary;
public static readonly Dictionary<int, string> NumberRomanDictionary;
static Roman()
{
RomanNumberDictionary = new Dictionary<char, int>
{
{ 'I', 1 },
{ 'V', 5 },
{ 'X', 10 },
{ 'L', 50 },
{ 'C', 100 },
{ 'D', 500 },
{ 'M', 1000 },
};
NumberRomanDictionary = new Dictionary<int, string>
{
{ 1000, "M" },
{ 900, "CM" },
{ 500, "D" },
{ 400, "CD" },
{ 100, "C" },
{ 90, "XC" },
{ 50, "L" },
{ 40, "XL" },
{ 10, "X" },
{ 9, "IX" },
{ 5, "V" },
{ 4, "IV" },
{ 1, "I" },
};
}
public static string To(int number)
{
var roman = new StringBuilder();
foreach (var item in NumberRomanDictionary)
{
while (number >= item.Key)
{
roman.Append(item.Value);
number -= item.Key;
}
}
return roman.ToString();
}
public static int From(string roman)
{
int total = 0;
int current, previous = 0;
char currentRoman, previousRoman = '\0';
for (int i = 0; i < roman.Length; i++)
{
currentRoman = roman[i];
previous = previousRoman != '\0' ? RomanNumberDictionary[previousRoman] : '\0';
current = RomanNumberDictionary[currentRoman];
if (previous != 0 && current > previous)
{
total = total - (2 * previous) + current;
}
else
{
total += current;
}
previousRoman = currentRoman;
}
return total;
}
}
Some Unit Tests for To
method:
[TestClass]
public class DecimalToRomanTest
{
[TestMethod]
public void Roman_1_I()
{
Assert.AreEqual("I", Roman.To(1));
}
[TestMethod]
public void Roman_2_II()
{
Assert.AreEqual("II", Roman.To(2));
}
[TestMethod]
public void Roman_3_III()
{
Assert.AreEqual("III", Roman.To(3));
}
[TestMethod]
public void Roman_4_IV()
{
Assert.AreEqual("IV", Roman.To(4));
}
[TestMethod]
public void Roman_5_V()
{
Assert.AreEqual("V", Roman.To(5));
}
[TestMethod]
public void Roman_9_IX()
{
Assert.AreEqual("IX", Roman.To(9));
}
[TestMethod]
public void Roman_10_X()
{
Assert.AreEqual("X", Roman.To(10));
}
[TestMethod]
public void Roman_49_XLIX()
{
Assert.AreEqual("XLIX", Roman.To(49));
}
[TestMethod]
public void Roman_50_L()
{
Assert.AreEqual("L", Roman.To(50));
}
[TestMethod]
public void Roman_100_C()
{
Assert.AreEqual("C", Roman.To(100));
}
[TestMethod]
public void Roman_400_CD()
{
Assert.AreEqual("CD", Roman.To(400));
}
[TestMethod]
public void Roman_500_D()
{
Assert.AreEqual("D", Roman.To(500));
}
[TestMethod]
public void Roman_900_CM()
{
Assert.AreEqual("CM", Roman.To(900));
}
[TestMethod]
public void Roman_1000_M()
{
Assert.AreEqual("M", Roman.To(1000));
}
[TestMethod]
public void Roman_11984_MMMMMMMMMMMCMLXXXIV()
{
Assert.AreEqual("MMMMMMMMMMMCMLXXXIV", Roman.To(11984));
}
}
Some Unit Tests for From
method:
[TestClass]
public class RomanToDecimalTest
{
[TestMethod]
public void Roman_I_1()
{
Assert.AreEqual(1, Roman.From("I"));
}
[TestMethod]
public void Roman_II_2()
{
Assert.AreEqual(2, Roman.From("II"));
}
[TestMethod]
public void Roman_III_3()
{
Assert.AreEqual(3, Roman.From("III"));
}
[TestMethod]
public void Roman_IV_4()
{
Assert.AreEqual(4, Roman.From("IV"));
}
[TestMethod]
public void Roman_V_5()
{
Assert.AreEqual(5, Roman.From("V"));
}
[TestMethod]
public void Roman_IX_9()
{
Assert.AreEqual(9, Roman.From("IX"));
}
[TestMethod]
public void Roman_X_10()
{
Assert.AreEqual(10, Roman.From("X"));
}
[TestMethod]
public void Roman_XLIX_49()
{
Assert.AreEqual(49, Roman.From("XLIX"));
}
[TestMethod]
public void Roman_L_50()
{
Assert.AreEqual(50, Roman.From("L"));
}
[TestMethod]
public void Roman_C_100()
{
Assert.AreEqual(100, Roman.From("C"));
}
[TestMethod]
public void Roman_CD_400()
{
Assert.AreEqual(400, Roman.From("CD"));
}
[TestMethod]
public void Roman_D_500()
{
Assert.AreEqual(500, Roman.From("D"));
}
[TestMethod]
public void Roman_CM_900()
{
Assert.AreEqual(900, Roman.From("CM"));
}
[TestMethod]
public void Roman_M_1000()
{
Assert.AreEqual(1000, Roman.From("M"));
}
[TestMethod]
public void Roman_MMMMMMMMMMMCMLXXXIV_11984()
{
Assert.AreEqual(11984, Roman.From("MMMMMMMMMMMCMLXXXIV"));
}
}
I find BrunoLM's code very simple and elegant but the From(...) function
really needs to check if the source is a valid roman numeral.
Something like this
public static bool IsValidRomanNumber(string source) {
bool result = true;
string[] invalidCouples = { "VV", "LL", "DD", "VX", "VC", "VM", "LC", "LM", "DM", "IC", "IM", "XM" };
foreach (string s in invalidCouples) {
if (source.Contains(s)) {
result = false;
break;
}
}
return result;
}
Here is the version from @Cammilius converted to C# - works for me on low numbers which is all I need for my usecase.
public String convertToRoman(int num)
{
//Roman numerals to have <= 3 consecutive characters, the distances between deciaml values conform to this
int[] decimalValue = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 };
string[] romanNumeral = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
int num_cp = num; // copy the function parameter into num_cp
String result = "";
for (var i = 0; i < decimalValue.Length; i = i + 1)
{ //itarate through array of decimal values
//iterate more to find values not explicitly provided in the decimalValue array
while (decimalValue[i] <= num_cp)
{
result = result + romanNumeral[i];
num_cp = num_cp - decimalValue[i];
}
}
return result;
}
# checks if given roman number is valid, empty means 0
Function IsRoman {
[OutputType([Boolean])]
Param([String] $roman)
return ($roman -ne $Null) -and ($roman -match ("^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"));
}
# checks if given arabic number is valid, less than 0 or floating point is not possible and means error
Function IsConvertibleToRoman {
[OutputType([Boolean])]
Param([String] $arabic)
Try {
$result = $arabic -match "^\-?(\d+\.?\d*)(e\-?\d+)?$" -and [Double]$arabic -eq [Long]$arabic -and [Long]$arabic -ge 0;
}
Catch {
# this extra check is necessary because conversion to double fails with hex numbers
return $arabic -match "^0x[0-9a-f]+$";
}
return $result;
}
# convert arabic numerals into roman numerals, including validity check, allow three ciphres as maximum
Function ArabicToRoman {
[OutputType([String])]
Param([String] $arabicNumber)
If (-not (IsConvertibleToRoman $arabicNumber)) {
[String]$errorMessage = "can not convert '$arabicNumber' into roman number!";
Write-Error $errorMessage -Category InvalidArgument;
return $Null;
}
[Long]$number = $arabicNumber;
# ---------------------------------------------------------------
[String]$romanNumber = "M"*([math]::Floor($number / 1000));
# ---------------------------------------------------------------
$number = $number % 1000;
If ($number -ge 500) {
If ($number -ge 900) {
$romanNumber = $romanNumber + "CM";
$number = $number - 900;
} Else {
$romanNumber = $romanNumber + "D";
$number = $number - 500;
}
}
If ($number -lt 400) {
$romanNumber = $romanNumber + "C"*([math]::Floor($number / 100));
} Else {
$romanNumber = $romanNumber + "CD";
}
# ---------------------------------------------------------------
$number = $number % 100;
If ($number -ge 50) {
If ($number -ge 90) {
$romanNumber = $romanNumber + "XC";
$number = $number - 90;
} Else {
$romanNumber = $romanNumber + "L";
$number = $number - 50;
}
}
If ($number -lt 40) {
$romanNumber = $romanNumber + "X"*([math]::Floor($number / 10));
} Else {
$romanNumber = $romanNumber + "XL";
}
# ---------------------------------------------------------------
$number = $number % 10;
If ($number -ge 5) {
If ($number -eq 9) {
$romanNumber = $romanNumber + "IX";
return $romanNumber;
} Else {
$romanNumber = $romanNumber + "V";
$number = $number - 5;
}
}
If ($number -lt 4) {
$romanNumber = $romanNumber + "I"*$number;
} Else {
$romanNumber = $romanNumber + "IV";
}
# ---------------------------------------------------------------
return $romanNumber;
}
# convert roman numerals into arabic numerals, including validity check, evaluation from left to right
Function RomanToArabic {
[OutputType([Int])]
Param([String] $romanNumber)
[long]$arab = 0;
[char]$lastCipher = $Null;
If (-not (isRoman $romanNumber)) {
[String]$errorMessage = "'$romanNumber' is not a roman numeral!";
Write-Error $errorMessage -Category InvalidArgument
return -1;
}
Foreach($aCipher In $romanNumber.ToUpper().ToCharArray()) {
Switch ($aCipher) {
'I' {
$arab += 1;
Break;
}
'V' {
If ($lastCipher -eq 'I') {
$arab += 4 - 1;
} Else {
$arab += 5;
}
Break;
}
'X' {
If ($lastCipher -eq 'I') {
$arab += 9 - 1;
} Else {
$arab += 10;
}
Break;
}
'L' {
If ($lastCipher -eq 'X') {
$arab += 40 - 10;
} Else {
$arab += 50;
}
Break;
}
'C' {
If ($lastCipher -eq 'X') {
$arab += 90 - 10;
} Else {
$arab += 100;
}
Break;
}
'D' {
If ($lastCipher -eq 'C') {
$arab += 400 - 100;
} Else {
$arab += 500;
}
Break;
}
'M' {
If ($lastCipher -eq 'C') {
$arab += 900 - 100;
} Else {
$arab += 1000;
}
Break;
}
}
$lastCipher = $aCipher;
}
return $arab;
}
Please have a look here for further information:
https://github.com/CBM6502/Conversion-Test