Converting integers to roman numerals
Asked Answered
A

32

66

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 and easy way to do this conversion, or extend my existing code so that it handles all cases?

static string convertroman(int number)
    {
        int l = number / 10;
        StringBuilder sb = new StringBuilder();
        for (int m = 0; m <= l; m++)
        {
            if (l == 0)
            {
                break;
            }
            if (l == 5)
            {
                sb = sb.Append(ro.L.ToString());
                break;
            }
            if (l == 4)
            {
                sb = sb.Append(ro.X.ToString()).Append(ro.L.ToString());
                break;
            }
            if (l == 9)
            {
                sb = sb.Append(ro.X.ToString()).Append(ro.C.ToString());
                break;
            }
            if (l == 10)
            {
                sb = sb.Append(ro.C.ToString());
                break;
            }

            if (l > 5 && l < 9)
            {
                sb = sb.Append(ro.L.ToString());
                l = l - 5;
                m = 0;
                // break;
                continue;
            }
            if (l > 10)
            {
                sb = sb.Append(ro.C.ToString());
                l = l - 10;
                m = 0;
                // continue;

            }
            else
            {
                sb = sb.Append(ro.X.ToString());
            }

        }
        int z = number % 10;
        for (int x = 0; x <= z; x++)
        {
            if (z == 0)
            {
                break;
            }
            if (z == 5)
            {
                sb = sb.Append(ro.V.ToString());
                break;
            }
            if (z == 4)
            {
                sb = sb.Append(ro.I.ToString()).Append(ro.V.ToString());
                break;
            }
            if (z == 9)
            {
                sb = sb.Append(ro.I.ToString()).Append(ro.X.ToString());
                break;
            }
            if (z == 10)
            {
                sb = sb.Append(ro.X.ToString());
                break;
            }
            if (z > 5 && z < 9)
            {
                sb = sb.Append(ro.V.ToString());
                z = z - 5;
                x = 0;
            }
            else
            {
                sb.Append(ro.I.ToString());
            }              

        }
        return sb.ToString();           
    }
Accursed answered 12/8, 2011 at 12:46 Comment(5)
You may be interested in this JavaScript Roman Numeral ConverterEssonite
Take a look at this SO question: #4987021Charlatan
Have a look at this nice blog post.Buttons
I needed this for a non-homework reason. I'm dynamically generating a (non-HTML) outline.Interlunation
See gist.github.com/wollmich/fb85d529b195a8008e940e488cb739beTwister
B
131

Try this, simple and compact:

public static string ToRoman(int number)
{
    if ((number < 0) || (number > 3999)) throw new ArgumentOutOfRangeException(nameof(number), "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 UnreachableException("Impossible state reached");
}
Bradybradycardia answered 31/7, 2012 at 22:0 Comment(7)
Does your throw exception stop program if "something bad happened"? The purpose of this function is to return only a value, so it should has at least a try-catch to maintain a return value.Stedman
why is recursion bad? I thought this was quite a neat solutionQuent
Recursion isn't "bad" so long as there's a good reason. It does have some downsides. For instance, notice the upper limit of 3999 in the above code. If you remove this limit and throw a large number at this method, you can get a stack overflow (no pun intended) pretty quickly. And, while I like the simplicity of this design, it's not very efficient from a memory standpoint due to the heavy string concatenation.Bronze
@MikeU This method returns at least 1 character for each layer it recurs. At best, it's recursion depth will be the length of the final string returned. It's doubtful you'd get a StackOverflowException and if you did, the final Roman Numeral would be too long to be useful anyway. (For larger numbers, you'd but a line over a given character to represent 1000 times that character)Overalls
@Overalls you're not wrong. The solution is definitely clever, but I don't see the point of using recursion for something that can easily be accomplished with a "while" loop and a string builder. That not only eliminates the stack issue, but it's also slightly more performant and memory efficient because you can use a StringBuilder rather than concatenating strings and you don't have all the extraneous method calls.Bronze
But... the stack depth here is O(log(n)). Additionally, Roman numbers are useful up to a few digits, maybe 8? Is the difference versus a StringBuilder even measurable? With that reasoning, functional programming should be banned and everything should be implemented in assembler, because only then you'll utilize the full power of your machine. It can lead to absurd.Niggle
The stack overflow would only occur if you removed the upperbound and passed in a huge argument, resulting in "MMMMMMMMMMMMMMMMM..." etc.Curate
H
26

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"));
    }
}
Hagen answered 26/2, 2014 at 11:17 Comment(3)
Note: As @abc667 mentioned.The NumberRomanDictionary dictionary is missing one key value [90 : "XC"] !!!Thorianite
Isn't the order of dictionaries non-deterministic? If so, what happens if 'I' comes out first? Wouldn't that just keep printing I over and over until it reached zero? Also, you don't check for negatives.Mathis
You are right. The order is undefined. The program should use arrays or lists instead. From the docs: For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair<TKey,TValue> structure representing a value and its key. The order in which the items are returned is undefined.Daradarach
R
22

Here's a much simpler algorithm - forgive me, I don't know C# so I'm writing this in JavaScript, but the same algorithm should apply (and I've commented so you can understand the algorithm):

function intToRoman(int) {

    // create 2-dimensional array, each inner array containing 
    // roman numeral representations of 1-9 in each respective 
    // place (ones, tens, hundreds, etc...currently this handles
    // integers from 1-3999, but could be easily extended)
    var romanNumerals = [
        ['', 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix'], // ones
        ['', 'x', 'xx', 'xxx', 'xl', 'l', 'lx', 'lxx', 'lxxx', 'xc'], // tens
        ['', 'c', 'cc', 'ccc', 'cd', 'd', 'dc', 'dcc', 'dccc', 'cm'], // hundreds
        ['', 'm', 'mm', 'mmm'] // thousands
    ];

    // split integer string into array and reverse array
    var intArr = int.toString().split('').reverse(),
        len = intArr.length,
        romanNumeral = '',
        i = len;

    // starting with the highest place (for 3046, it would be the thousands 
    // place, or 3), get the roman numeral representation for that place 
    // and append it to the final roman numeral string
    while (i--) {
        romanNumeral += romanNumerals[ i ][ intArr[i] ];
    }

    return romanNumeral;

}

console.log( intToRoman(3046) ); // outputs mmmxlvi
Riannon answered 16/9, 2011 at 13:50 Comment(2)
A functional approach return int.toString().split("").reverse().reduce((acc, x, i) => (i < 3 ? romanNumerals[i][x] : "M".repeat(x)) + acc, "");. Works up to 1-9999.Succubus
If you want a C# version of this, I just submitted it in an answer, below. I would have put it here, but it was too long for a comment.Genome
P
15

This is actually quite a fun problem, and based on the reverse example on dofactory.com (turning roman numerals to decimals) its quite easy to reverse the pattern, and perhaps improve it a little. This code will support numbers from 1 to 3999999.

Begin with a context class, this defines the I/O of the parser

public class Context
{
    private int _input;
    private string _output;

    public Context(int input)
    {
        this._input = input;
    }

    public int Input
    {
        get { return _input; }
        set { _input = value; }
    }

    public string Output
    {
        get { return _output; }
        set { _output = value; }
    }
}

And an abstract expression, which defines the parsing operation

public abstract class Expression
{
    public abstract void Interpret(Context value);
}

Now, you need an abstract terminal expression, which defines the actual operation that will be performed:

public abstract class TerminalExpression : Expression
{
    public override void Interpret(Context value)
    {
        while (value.Input - 9 * Multiplier() >= 0)
        {
            value.Output += Nine();
            value.Input -= 9 * Multiplier();
        }
        while (value.Input - 5 * Multiplier() >= 0)
        {
            value.Output += Five();
            value.Input -= 5 * Multiplier();
        }
        while (value.Input - 4 * Multiplier() >= 0)
        {
            value.Output += Four();
            value.Input -= 4 * Multiplier();
        }
        while (value.Input - Multiplier() >= 0)
        {
            value.Output += One();
            value.Input -= Multiplier();
        }
    }

    public abstract string One();
    public abstract string Four();
    public abstract string Five();
    public abstract string Nine();
    public abstract int Multiplier();
}

Then, classes which define the behaviour of roman numerals (note, ive used the convention of lowercase where roman numerals use a bar over the letter to denote 1000 times)

class MillionExpression : TerminalExpression
{
    public override string One() { return "m"; }
    public override string Four() { return ""; }
    public override string Five() { return ""; }
    public override string Nine() { return ""; }
    public override int Multiplier() { return 1000000; }
}
class HundredThousandExpression : TerminalExpression
{
    public override string One() { return "c"; }
    public override string Four() { return "cd"; }
    public override string Five() { return "d"; }
    public override string Nine() { return "cm"; }
    public override int Multiplier() { return 100000; }
}
class ThousandExpression : TerminalExpression
{
    public override string One() { return "M"; }
    public override string Four() { return "Mv"; }
    public override string Five() { return "v"; }
    public override string Nine() { return "Mx"; }
    public override int Multiplier() { return 1000; }
}
class HundredExpression : TerminalExpression
{
    public override string One() { return "C"; }
    public override string Four() { return "CD"; }
    public override string Five() { return "D"; }
    public override string Nine() { return "CM"; }
    public override int Multiplier() { return 100; }
}
class TenExpression : TerminalExpression
{
    public override string One() { return "X"; }
    public override string Four() { return "XL"; }
    public override string Five() { return "L"; }
    public override string Nine() { return "XC"; }
    public override int Multiplier() { return 10; }
}
class OneExpression : TerminalExpression
{
    public override string One() { return "I"; }
    public override string Four() { return "IV"; }
    public override string Five() { return "V"; }
    public override string Nine() { return "IX"; }
    public override int Multiplier() { return 1; }
}

Almost there, we need a Non-terminal expression which contains the parse tree:

public class DecimalToRomaNumeralParser : Expression
{
    private List<Expression> expressionTree = new List<Expression>()
                                                  {
                                                      new MillionExpression(),
                                                      new HundredThousandExpression(),
                                                      new TenThousandExpression(),
                                                      new ThousandExpression(),
                                                      new HundredExpression(),
                                                      new TenExpression(),
                                                      new OneExpression()
                                                  };

    public override void Interpret(Context value)
    {
        foreach (Expression exp in expressionTree)
        {
             exp.Interpret(value);
        }
    }
}

Lastly, the client code:

Context ctx = new Context(123);
var parser = new DecimalToRomaNumeralParser();
parser.Interpret(ctx);
Console.WriteLine(ctx.Output); // Outputs CXXIII

Live example: http://rextester.com/rundotnet?code=JJBYW89744

Patroon answered 12/8, 2011 at 14:4 Comment(8)
why is the code limited to a max input of 3999999? How hard would it be to have it work up to Int32.MaxValue?Avery
Why is it wrong? You can have a crazily long decimal digit of the same length, doesn't mean it's wrong. There is no maximum number in roman numerals, similar to decimal, you can string digits together to make whatever integer you want.Avery
@DavidKlempfner that url is wrong or you don't see the complete value.Stillmann
@Leandro just because you can't see the complete value doesn't mean it doesn't exist. I'd like to know why Jamiec thinks you can't write Roman Numerals past 3999999.Avery
@Patroon it'd be 2147 lots of _M, and then CDLXXXMMMDCXLVII. Why do you think there is a limit with Roman Numerals? Taken straight from calculatorsoup.com/calculators/conversions/…: "You can represent numbers larger than 3,999 in Roman numerals using an overline"Avery
@DavidKlempfner I didn't tell you that there is no existence of that number, I tell you that the application at that url doesn't show the number, only a bunch of "M", you can see the raw html code source and is trunked: MMMMMMMMMM </div>Stillmann
@DavidKlempfner I think for the purpose of this answer, it's entirely irrelevant. If you want to expand this code to support larger numbers it should be pretty straightforward - an exercise for the reader.Patroon
Hey @Jamiec, you are missing the TenThousandExpression class with his implementation.Fredella
L
12

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");
}
Lemmuela answered 18/3, 2016 at 15:59 Comment(1)
Clever -- I like this approach. :)Moorefield
E
8

This should be the simplest solution.

public string IntToRoman(int num)
{
    var result = string.Empty;
    var map = new Dictionary<string, int>
    {
        {"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}
    };
    foreach (var pair in map)
    {
        result += string.Join(string.Empty, Enumerable.Repeat(pair.Key, num / pair.Value));
        num %= pair.Value;
    }
    return result;
}
Esbensen answered 14/5, 2015 at 8:11 Comment(3)
why did you limit the thousands column to 3000?Avery
@DavidKlempfner previous solution had a constraint. I have updated it to be generic.Esbensen
The logic is simple, but you are probably best to replace the string.Join and the string concatenation with a string builder. That would be more efficient, but would retain the simplicityTraceytrachea
S
6

Here is a slim solution from DotNetSnippets

private string ToRomanNumber(int number)
{
    StringBuilder result = new StringBuilder();
    int[] digitsValues = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
    string[] romanDigits = { "I", "IV", "V", "IX", "X", "XL", "L", "XC", "C", "CD", "D", "CM", "M" };
    while (number > 0)
    {
        for (int i = digitsValues.Count() - 1; i >= 0; i--)
            if (number / digitsValues[i] >= 1)
            {
                number -= digitsValues[i];
                result.Append(romanDigits[i]);
                break;
            }
    }
    return result.ToString();
}

same approach with a Dictionary

private string ToRomanNumber(int number)
{
    string result = string.Empty;
    Dictionary<int, string> roman = new Dictionary<int, string>() { { 1, "I" }, { 4, "IV" }, { 5, "V" }, { 9, "IX" }, { 10, "X" }, { 40, "XL" }, { 50, "L" }, { 90, "XC" }, { 100, "C" }, { 400, "CD" }, { 500, "D" }, { 900, "CM" }, { 1000, "M" } };
    while (number > 0)
        foreach (var item in roman.OrderByDescending(x => x.Key))
            if (number / item.Key >= 1)
            {
                number -= item.Key;
                result += item.Value;
                break;
            }
    return result;
}
Sisco answered 10/7, 2014 at 8:53 Comment(0)
K
5

This version doesn't "cheat" as others: it generates internally the "base" table with all the "base" "composable" numbers. For lazyness I'm using Tuples, 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);
    }
}
Kerrikerrie answered 20/10, 2011 at 14:19 Comment(1)
+1 for a great solution that doesn't cheat by having hard coded values such as "XC" everywhere. Well done!Avery
B
5

While I liked Mosè Bottacini's answer, using recursion has a couple of negative side effects in this scenario. One being the possible stack overflow, hence his limiting of the upper bound of the number. While, yes, I realize how ridiculous a huge number looks in roman numerals, this is still a limitation that is not necessary to achieve the result.

Also, since strings are immutable, his version is going to be very memory inefficient, due to the heavy use of string concatenation. Below is my modified version of his method, using just a while loop and a StringBuilder. My version should actually be more performant (although we're talking about differences in the sub-millisecond range) and much easier on system memory.

public static string ToRomanNumeral(this int value)
{
    if (value < 0)
        throw new ArgumentOutOfRangeException("Please use a positive integer greater than zero.");

    StringBuilder sb = new StringBuilder();
    int remain = value;
    while (remain > 0)
    {
        if (remain >= 1000) { sb.Append("M"); remain -= 1000; }
        else if (remain >= 900) { sb.Append("CM"); remain -= 900; }
        else if (remain >= 500) { sb.Append("D"); remain -= 500; }
        else if (remain >= 400) { sb.Append("CD"); remain -= 400; }
        else if (remain >= 100) { sb.Append("C"); remain -= 100; }
        else if (remain >= 90) { sb.Append("XC"); remain -= 90; }
        else if (remain >= 50) { sb.Append("L"); remain -= 50; }
        else if (remain >= 40) { sb.Append("XL"); remain -= 40; }
        else if (remain >= 10) { sb.Append("X"); remain -= 10; }
        else if (remain >= 9) { sb.Append("IX"); remain -= 9; }
        else if (remain >= 5) { sb.Append("V"); remain -= 5; }
        else if (remain >= 4) { sb.Append("IV"); remain -= 4; }
        else if (remain >= 1) { sb.Append("I"); remain -= 1; }
        else throw new Exception("Unexpected error."); // <<-- shouldn't be possble to get here, but it ensures that we will never have an infinite loop (in case the computer is on crack that day).
    }

    return sb.ToString();
}
Bronze answered 25/4, 2014 at 21:36 Comment(2)
In a quick performance test, I had both methods count to 3999 (since that's the upper limit of Mosè Bottacini's method). Recursive method calc'd 0-3999 in roman numerals in 3 milliseconds. Mine did it in 2 milliseconds. So, yeah, not a lot of performance difference.Bronze
The reason for limiting to 3999 is because after 3999 you use the top bar notation. Top bar = 1,000's, Top & Bottom Bar = 1,000,000's.Knipe
D
4

A string representation of the number's corresponding roman numeral.

    public static string ToRomanNumeral(this int number)
    {

        var retVal = new StringBuilder(5);
        var valueMap = new SortedDictionary<int, string>
                           {
                               { 1, "I" },
                               { 4, "IV" },
                               { 5, "V" },
                               { 9, "IX" },
                               { 10, "X" },
                               { 40, "XL" },
                               { 50, "L" },
                               { 90, "XC" },
                               { 100, "C" },
                               { 400, "CD" },
                               { 500, "D" },
                               { 900, "CM" },
                               { 1000, "M" },
                           };

        foreach (var kvp in valueMap.Reverse())
        {
            while (number >= kvp.Key)
            {
                number -= kvp.Key;
                retVal.Append(kvp.Value);
            }
        }

        return retVal.ToString();
    }
Desertion answered 11/3, 2019 at 9:42 Comment(1)
Actually, you are not using the dictionary as a dictionary, so you could replace it with (already reversed) List<KeyValuePair<int, string>>.Twickenham
F
3

Far too late, probably you already solved this, however this is an algorithm which can do the trick for you as well.

Before you start, you could simply do the analysis on Roman literals. For the known ASCII set, only values between 0 and 4000 are supported. If you like to go beyond, you could define your own roman literal then.

Before we start, we know that with the given range above, we can form a roman string from seven occurrences of Roman Literals (I,V,X,L,C,D and M).

Therefore we start with a simple look-up table, based on indices which are calculated in another function. Unknown indices are returned as a white-space character. As I wrote above, one might add additional characters when needed:

    /// <summary>
    /// Helper method that looks up a given index to it's roman value.
    /// </summary>
    /// <param name="decimalValue"></param>
    /// <returns>The roman literal corresponding to it's index</returns>
    private char DecimalToRoman(int index)
    {
        switch (index)
        {
            case 1: return 'I';
            case 2: return 'V';
            case 3: return 'X';
            case 4: return 'L';
            case 5: return 'C';
            case 6: return 'D';
            case 7: return 'M';
            default: return ' ';
        }
    }

The real conversion will happen here:

    private string ConvertToRoman(string input)
    {
        int index = 0;
        string output = "";

        for (int i = 0; i < input.Length; i++)
        {
            //Some magic here, this formula will calculate the correct starting
            //index of the roman literal to find in the look-up table.
            //Since units, tens and hundreds (up to thousand) can be formed of
            //three roman literals, we need three indices for looking up the
            //correct roman literal.
            index = 2 * (input.Length - (i + 1)) + 1;

            char digit1 = DecimalToRoman(index);
            char digit2 = DecimalToRoman(index + 1);
            char digit3 = DecimalToRoman(index + 2);

            int originalValue = System.Convert.ToInt32(input[i] - '0');

            switch (originalValue)
            {
                case 1:
                case 2:
                case 3: for (int j = 0; j < originalValue; j++)
                        output += digit1.ToString();
                    break;
                case 4: output += digit1.ToString() + digit2.ToString();
                    break;
                case 5: output += digit2.ToString();
                    break;
                case 6:
                case 7:
                case 8: output += digit2.ToString();
                    for (int j = 0; j < originalValue - 5; j++)
                        output += digit1.ToString();
                    break;
                case 9: output += digit1.ToString() + digit3.ToString();
                    break;
            }              
        }
        return output;
    }

That is it. If you look for more OO Designed approaches, please accept the answers above this post. There are just a lot of ways to solve this approach.

EDIT: Note that this solution does not cheat (just looking up all occurences of roman literals) as well :)

Farnham answered 8/12, 2011 at 10:1 Comment(1)
@JackGriffin I meant three roman literals, thus four 'C''s aren't allowed.Farnham
O
2

I gave it a try and my solution looks like this:

public class RomanNumeral
{
    private readonly IDictionary<int, string> romanDictionary = new Dictionary<int, string> 
    {
        {1, "I"}, {5, "V"}, {10, "X"}, {50, "L"}, {100, "C"}, {500, "D"}, {1000, "M"}
    };

    private int factor = 1;

    public string Parse(int arabicNumber)
    {
        if (arabicNumber < 0) throw new ArgumentException();

        var romanNumerals = new List<string>();
        foreach (var number in arabicNumber.Split().Reverse())
        {
            romanNumerals.Insert(0, ToRoman(number));
            factor *= 10;
        }
        return romanNumerals.Concatenated();
    }

    private string ToRoman(int number)
    {
        if (number.In(4, 9)) return ToRoman(1) + ToRoman(number + 1);
        if (number.In(6, 7, 8)) return ToRoman(number - 1) + ToRoman(1);
        if (number.In(2, 3)) return ToRoman(1) + ToRoman(number - 1);
        if (number == 0) return string.Empty;
        return romanDictionary[number * factor];
    }
}
Onomasiology answered 22/5, 2015 at 17:58 Comment(0)
G
2

I just converted @jbyrd's solution into C#. Here it is:

public static class RomanNumeralExtension
{
    public static string ToRoman(this int value)
    {
        // create 2-dimensional array, each inner array containing 
        // roman numeral representations of 1-9 in each respective 
        // place (ones, tens, hundreds, etc...currently this handles
        // integers from 1-3999, but could be easily extended)
        var romanNumerals = new string[][] {
            new string[] {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}, // ones
            new string[] {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}, // tens
            new string[] {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}, // hundreds
            new string[] {"", "M", "MM", "MMM"} // thousands
        };

        // split integer string into array and reverse array
        var digits = value.ToString().ToCharArray();
        Array.Reverse(digits);

        // starting with the highest place (for 3046, it would be the thousands 
        // place, or 3), get the roman numeral representation for that place 
        // and append it to the final roman numeral string
        string romanNumeral = "";
        var i = digits.Length;
        while (i-- > 0) {
            romanNumeral += romanNumerals[ i ][ digits[i]-'0' ];
        }

        return romanNumeral;
    }
}
Genome answered 8/11, 2021 at 0:44 Comment(0)
A
1

I can provide a method which is comparatively simpler than the existing

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 
    }
}
Attract answered 22/7, 2014 at 11:59 Comment(0)
U
1

A neat, quick and straightforward solution

function convertToRoman(num) {

  //Roman numerals to have <= 3 consecutive characters, the distances between deciaml values conform to this
  var decimalValue = [ 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 ];
  var romanNumeral = [ 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I' ];
  var num_cp = num; // copy the function parameter into num_cp
  var result = '';

  for (var i=0; i < decimalValue.length; i++){ //itarate through array of decimal values
      //iterate more to find values not explicitly provided in the decimalValue array
    while (decimalValue[i] <= num_cp){
      result += romanNumeral[i];
      num_cp -= decimalValue[i];
    }
  }
  return result;
}

convertToRoman(477);
Usual answered 17/6, 2017 at 17:13 Comment(1)
I noticed you hard coded CM, CD, XC etc. What logic did you use in your head to work out that 900 was CM? Couldn't you use that logic you used in your head, in your program, to have fewer duplications of roman numeral symbols, thus having centralized points of control? If you want to change "C" to "c", you'd have to do it in many placesAvery
K
1

One more straight forward solution. Trying to improve slightly the performance I use StringBuilder, iterate through less keys (one the other site of course LINQ-where might add additional delay)

public class ArabicToRomanConverter
{
    private static readonly Dictionary<int, string> _romanDictionary = 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 ArabicToRomanConverter()
    {

    }

    public string Convert(int arabicNumber)
    {
        StringBuilder romanNumber = new StringBuilder();
        var keys = _romanDictionary.Keys.Where(k => arabicNumber >= k).ToList();
        for (int i = 0; i < keys.Count && arabicNumber > 0; i++)
        {
            int ckey = keys[i];
            int division = arabicNumber / ckey;
            if (division != 0)
            {
                for (int j = 0; j < division; j++)
                {
                    romanNumber.Append(_romanDictionary[ckey]);
                    arabicNumber -= ckey;
                }
            }
        }

        return romanNumber.ToString();
    }
}
Khudari answered 8/11, 2017 at 11:27 Comment(0)
N
1

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.

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;
    }
}
Neuroma answered 14/3, 2019 at 9:38 Comment(1)
Interesting idea, however the semantics check can also be performed by performing a double translation. E.g. first translate a roman numeral to a decimal, then convert that back to a roman numeral. If the output of the last equals the input of the first, then you have quite an idea about how valid it is right?Farnham
A
0
 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;

    }

It would be easier if you reverse the roman numerals to handle the case like XIV. The code is refer from this blog.

Aerate answered 17/10, 2015 at 18:38 Comment(0)
A
0
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter the number\n");
            int num = int.Parse(Console.ReadLine());
            ToRomanNumber tr = new ToRomanNumber();
            string opt=tr.ToRoman(num);
            Console.WriteLine(opt);
        }
    }
    class ToRomanNumber
    {
        string s = "";

        public string ToRoman(int number)
        {

            if ((number < 0) || (number > 3999))
            {
                s = s + "Invalid Input";
            }
            if (number < 1) return s;
            if (number >= 1000) { s = s + "M"; ToRoman(number - 1000);}
            if (number >= 900){ s = s + "CM";ToRoman(number - 900);}
            if (number >= 500){ s = s + "D"; ToRoman(number - 500);}
            if (number >= 400){ s = s + "CD"; ToRoman(number - 400);}
            if (number >= 100){ s = s + "C"; ToRoman(number - 100);}
            if (number >= 90){ s = s + "XC"; ToRoman(number - 90);}
            if (number >= 50){ s = s + "L";ToRoman(number - 50);}
            if (number >= 40){ s = s + "XL";ToRoman(number - 40);}
            if (number >= 10){ s = s + "X"; ToRoman(number - 10); }
            if (number >= 9) { s = s + "IX"; ToRoman(number - 9); }
            if (number >= 5) { s = s + "V"; ToRoman(number - 5); }
            if (number >= 4) { s = s + "IV"; ToRoman(number - 4); }
            if (number >= 1) { s = s + "I"; ToRoman(number - 1);}
            return s;
        }
    }
}
Anacrusis answered 26/1, 2016 at 14:33 Comment(0)
B
0

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;
    }
Battleax answered 18/2, 2016 at 10:56 Comment(0)
S
0
        Random r = new Random();
        int[] arreglo = new int[100];
        for (int i=0; i<arreglo.Length;i++) {
            arreglo[i] = r.Next(1,1001);
        }

        for (int t = 0;t < arreglo.Length; t++)
        {
            if (arreglo[t] >= 1000)
            {
                Console.Write("M"); arreglo[t] -= 1000;
            }
            if (arreglo[t] >=900)
            {
                Console.Write("MC"); arreglo[t] -= 900;
            }
            if (arreglo[t] >= 500)
            {
                Console.Write("D"); arreglo[t] -= 500;
            }
            if (arreglo[t] >= 400)
            {
                Console.Write("CD"); arreglo[t] -= 400;
            }
            if (arreglo[t] >= 100) {
                Console.Write("C"); arreglo[t] -= 100;
            }
            if (arreglo[t] >= 90)
            {
                Console.Write("XC"); arreglo[t] -= 90;
            }
            if (arreglo[t] >= 50)
            {
                Console.Write("L"); arreglo[t] -= 50;
            }
            if (arreglo[t] >= 40)
            {
                Console.Write("XL"); arreglo[t] -= 40;
            }
            if (arreglo[t] >= 10)
            {
                Console.Write("X"); arreglo[t] -= 10;
            }
            if (arreglo[t] >= 9)
            {
                Console.Write("IX"); arreglo[t] -= 9;
            }
            if (arreglo[t] >= 5)
            {
                Console.Write("V"); arreglo[t] -= 5;
            }
            if (arreglo[t] >= 4)
            {
                Console.Write("IV"); arreglo[t] -= 4;
            }
            if (arreglo[t] >= 1)
            {
                Console.Write("I"); arreglo[t] -= 1;
            }

            Console.WriteLine();

        }
        Console.ReadKey();    
Siqueiros answered 6/10, 2016 at 16:50 Comment(0)
A
0

The solution is lengthy, but easy to understand for a beginner. Process up to 3000

namespace RomansTranslator
{
    using System;
    using System.Collections.Generic;
    /// <summary>
    /// Accepts a number (between 1 and 3000) and prints its Roman equivalent.
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            string number = string.Empty;
            Console.Write("Enter the Numeric number : ");
            number = Console.ReadLine();
            if (IsValid(number)) // Validates the input
            {
                string roman = ConvertToRoman(number);
                Console.WriteLine("Roman Number is " + roman);
            }
            else
            {
                Console.WriteLine("Invalid Number");
            }
            Console.ReadKey();
        }

        private static string ConvertToRoman(string numberString)
        {
            string romanValue = string.Empty;
            int number = Convert.ToInt32(numberString);
            if (number >= 1)
            {
                // Loop through each roman character from highest 
                foreach (int item in RomanDictionary().Keys)
                {
                    while (number >= item)
                    {
                        romanValue = romanValue + RomanString(item);
                        number -= item;
                    }
                }
            }
            return romanValue;
        }

        /// <summary>
        /// Returns Roman Equvalent
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        private static string RomanString(int n)
        {
            string romanString = string.Empty;
            romanString = RomanDictionary()[n].ToString();
            return romanString;
        }

        /// <summary>
        /// List of Roman Characters 
        /// </summary>
        /// <returns></returns>
        private static Dictionary<int, string> RomanDictionary()
        {

            Dictionary<int, string> romanDic = new Dictionary<int, string>();
            romanDic.Add(1000, "M");
            romanDic.Add(900, "CM");
            romanDic.Add(500, "D");
            romanDic.Add(400, "CD");
            romanDic.Add(100, "C");
            romanDic.Add(90, "XC");
            romanDic.Add(50, "L");
            romanDic.Add(40, "XL");
            romanDic.Add(10, "X");
            romanDic.Add(9, "IX");
            romanDic.Add(5, "V");
            romanDic.Add(4, "IV");
            romanDic.Add(1, "I");
            return romanDic;
        }


        /// <summary>
        /// Validates the Input
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private static bool IsValid(string input)
        {
            int value = 0;
            bool isValid = false;
            if (int.TryParse(input, out value))
            {
                if (value <= 3000)
                {
                    isValid = true;
                }
            }
            return isValid;
        }
    }
}
Absorptivity answered 22/3, 2017 at 2:3 Comment(0)
V
0

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;
}
Vollmer answered 24/10, 2017 at 23:6 Comment(3)
I didn't think about it at all as I was looking for some working code. I only posted a C# version of the answer by @Camilius. If I thought about it in my head I might decide that the hard coded values make for compact, understandable and maintainable code. The if-then logic to replace that hard coding would not be, imho.Vollmer
Compact and understandable, however it lacks the ability to have more than 3999999 as an input. There is no rule that you can't have more than 3 consecutive same letters in a row. This rule also doesn't exist in decimal values.Avery
The only rule is that if you can put a smaller letter in front of a larger letter, you must, and a consequence of this is that most of the time this prevents 3 consecutive same letters, however this doesn't apply when you're dealing with the largest roman numeral letter since there is no higher letter to put this in front of.Avery
M
0
public static String convert(int num)
{
    String[] charsArray  = {"I", "IV", "V", "IX", "X", "XL", "L", "XC","C","CD","D","CM","M" };
    int[] charValuesArray = {1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000};

    String resultString = "";
    int temp = num;
    int [] resultValues = new int[13];
    // Generate an array which "char" occurances count
    for(int i = 12 ; i >= 0 ; i--)
    {
        if((temp / charValuesArray[i]) > 0)
        {
            resultValues[i] = temp/charValuesArray[i];
            temp = temp % charValuesArray[i];
        }
    }
    // Print them if not occured do not print
    for(int j = 12 ; j >= 0 ; j--)
    {
        for(int k = 0 ; k < resultValues[j]; k++)
        {
            resultString+= charsArray[j];
        }
    }
    return resultString;

}
Meridithmeriel answered 5/4, 2018 at 7:11 Comment(0)
M
0

Thought this problem was interesting, this was my take on it.

It should (hopefully) deal with numbers up to the upper limit of overscored characters. Adding any other conventions should be just a matter of configuring new bands and adjusting the ConfigureNext chain.

NumeralGenerator.cs

public static class NumeralGenerator
{
    private static readonly INumeralBand RootNumeralBand = ConfigureMapping();

    private static INumeralBand ConfigureMapping()
    {
        var unitBand = new FinalBand(1, "I");

        var fiveBand = new NumeralBand(5, "V", unitBand);
        var tenBand = new NumeralBand(10, "X", unitBand);
        var fiftyBand = new NumeralBand(50, "L", tenBand);
        var hundredBand = new NumeralBand(100, "C", tenBand);
        var fiveHundredBand = new NumeralBand(500, "D", hundredBand);
        var thousandBand = new NumeralBand(1000, "M", hundredBand);

        var thousandUnitBand = new NumeralBand(1000, "I\u0305", thousandBand);

        var fiveThousandBand = new NumeralBand(5000, "V\u0305", thousandUnitBand);
        var tenThousandBand = new NumeralBand(10000, "X\u0305", thousandUnitBand);
        var fiftyThousandBand = new NumeralBand(50000, "L\u0305", tenThousandBand);
        var hundredThousandBand = new NumeralBand(100000, "C\u0305", tenThousandBand);
        var fiveHundredThousandBand = new NumeralBand(500000, "D\u0305", hundredThousandBand);
        var millionBand = new NumeralBand(1000000, "M\u0305", hundredThousandBand);

        millionBand
            .ConfigureNext(fiveHundredThousandBand)
            .ConfigureNext(hundredThousandBand)
            .ConfigureNext(fiftyThousandBand)
            .ConfigureNext(tenThousandBand)
            .ConfigureNext(fiveThousandBand)
            .ConfigureNext(thousandBand)
            .ConfigureNext(fiveHundredBand)
            .ConfigureNext(hundredBand)
            .ConfigureNext(fiftyBand)
            .ConfigureNext(tenBand)
            .ConfigureNext(fiveBand)
            .ConfigureNext(unitBand);

        return millionBand;
    }

    public static string ToNumeral(int number)
    {
        var numerals = new StringBuilder();

        RootNumeralBand.Process(number, numerals);

        return numerals.ToString();
    }
}

INumeralBand.cs

public interface INumeralBand
{
    int Value { get; }
    string Numeral { get; }

    void Process(int number, StringBuilder numerals);
}

NumeralBand.cs

public class NumeralBand : INumeralBand
{
    private readonly INumeralBand _negatedBy;
    private INumeralBand _nextBand;

    public NumeralBand(int value, string numeral, INumeralBand negatedBy)
    {
        _negatedBy = negatedBy;

        Value = value;
        Numeral = numeral;
    }

    public int Value { get; }
    public string Numeral { get; }

    public void Process(int number, StringBuilder numerals)
    {
        if (ShouldNegateAndStop(number))
        {
            numerals.Append(NegatedNumeral);
            return;
        }

        var numeralCount = Math.Abs(number / Value);
        var remainder = number % Value;

        numerals.Append(string.Concat(Enumerable.Range(1, numeralCount).Select(x => Numeral)));

        if (ShouldNegateAndContinue(remainder))
        {
            NegateAndContinue(numerals, remainder);
            return;
        }

        if (remainder > 0)
            _nextBand.Process(remainder, numerals);
    }

    private string NegatedNumeral => $"{_negatedBy.Numeral}{Numeral}";

    private bool ShouldNegateAndStop(int number) => number == Value - _negatedBy.Value;
    private bool ShouldNegateAndContinue(int number) => number >= Value - _negatedBy.Value;

    private void NegateAndContinue(StringBuilder stringBuilder, int remainder)
    {
        stringBuilder.Append(NegatedNumeral);
        remainder = remainder % (Value - _negatedBy.Value);

        _nextBand.Process(remainder, stringBuilder);
    }

    public T ConfigureNext<T>(T nextBand) where T : INumeralBand
    {
        _nextBand = nextBand;

        return nextBand;
    }
}

FinalBand.cs

public class FinalBand : INumeralBand
{
    public FinalBand(int value, string numeral)
    {
        Value = value;
        Numeral = numeral;
    }

    public int Value { get; }
    public string Numeral { get; }

    public void Process(int number, StringBuilder numerals)
    {
        numerals.Append(new string(Numeral[0], number));
    }
}

The Tests:

FinalBandTests.cs

public class FinalBandTests
{
    [Theory]
    [InlineData(1, "I")]
    [InlineData(2, "II")]
    [InlineData(3, "III")]
    [InlineData(4, "IIII")]
    public void Process(int number, string expected)
    {
        var stringBuilder = new StringBuilder();

        var numeralBand = new FinalBand(1, "I");
        numeralBand.Process(number, stringBuilder);

        Assert.Equal(expected, stringBuilder.ToString());
    }
}

NumeralBandTests.cs

public class NumeralBandTests
{
    private Mock<INumeralBand> _nextBand;
    private Mock<INumeralBand> _negatedBy;

    private StringBuilder _stringBuilder;

    public NumeralBandTests()
    {
        _stringBuilder = new StringBuilder();

        _nextBand = new Mock<INumeralBand>();
        _negatedBy = new Mock<INumeralBand>();
    }

    [Fact]
    public void Process_NegateAndStop()
    {
        var numeral = new NumeralBand(10, "X", _negatedBy.Object);

        _negatedBy.Setup(x => x.Value).Returns(1);
        _negatedBy.Setup(x => x.Numeral).Returns("I");

        numeral.Process(9, _stringBuilder);

        Assert.Equal("IX", _stringBuilder.ToString());
        _nextBand.Verify(x => x.Process(It.IsAny<int>(), It.IsAny<StringBuilder>()), Times.Never);
    }

    [Fact]
    public void Process_Exact()
    {
        var numeral = new NumeralBand(10, "X", _negatedBy.Object);

        _negatedBy.Setup(x => x.Value).Returns(1);
        _negatedBy.Setup(x => x.Numeral).Returns("I");

        numeral.Process(10, _stringBuilder);

        Assert.Equal("X", _stringBuilder.ToString());
        _nextBand.Verify(x => x.Process(It.IsAny<int>(), It.IsAny<StringBuilder>()), Times.Never);
    }

    [Fact]
    public void Process_NegateAndContinue()
    {
        var numeral = new NumeralBand(50, "L", _negatedBy.Object);
        numeral.ConfigureNext(_nextBand.Object);

        _negatedBy.Setup(x => x.Value).Returns(10);
        _negatedBy.Setup(x => x.Numeral).Returns("X");

        numeral.Process(54, _stringBuilder);

        Assert.Equal("L", _stringBuilder.ToString());
        _nextBand.Verify(x => x.Process(4, _stringBuilder), Times.Once);
    }
}

NumeralGeneratorTests.cs

public class NumeralGeneratorTests
{
    private readonly ITestOutputHelper _output;

    public NumeralGeneratorTests(ITestOutputHelper output)
    {
        _output = output;
    }

    [Theory]
    [InlineData(1, "I")]
    [InlineData(2, "II")]
    [InlineData(3, "III")]
    [InlineData(4, "IV")]
    [InlineData(5, "V")]
    [InlineData(6, "VI")]
    [InlineData(7, "VII")]
    [InlineData(8, "VIII")]
    [InlineData(9, "IX")]
    [InlineData(10, "X")]
    [InlineData(11, "XI")]
    [InlineData(15, "XV")]
    [InlineData(1490, "MCDXC")]
    [InlineData(1480, "MCDLXXX")]
    [InlineData(1580, "MDLXXX")]
    [InlineData(1590, "MDXC")]
    [InlineData(1594, "MDXCIV")]
    [InlineData(1294, "MCCXCIV")]
    [InlineData(3999, "MMMCMXCIX")]
    [InlineData(4000, "I\u0305V\u0305")]
    [InlineData(4001, "I\u0305V\u0305I")]
    [InlineData(5002, "V\u0305II")]
    [InlineData(10000, "X\u0305")]
    [InlineData(15000, "X\u0305V\u0305")]
    [InlineData(15494, "X\u0305V\u0305CDXCIV")]
    [InlineData(2468523, "M\u0305M\u0305C\u0305D\u0305L\u0305X\u0305V\u0305MMMDXXIII")]
    public void ToNumeral(int number, string expected)
    {
        var sw = Stopwatch.StartNew();
        var actual = NumeralGenerator.ToNumeral(number);
        sw.Stop();

        _output.WriteLine(sw.ElapsedMilliseconds.ToString());

        Assert.Equal(expected, actual);
    }
}
Mazuma answered 26/8, 2018 at 22:37 Comment(0)
T
0

@Backwards_Dave You wanted a solution that goes to max number, here you go:

public class ConvertDecimalNumberToRomanNumberType
{
    public class RomanNumberType
    {
        public string RomanNumber;
        public int RomanNumberValue;
    }

    public List<RomanNumberType> RomanNumbers;

    public void Initialize()
    {
        RomanNumbers = new List<RomanNumberType>();

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "M", RomanNumberValue = 1000 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "CM", RomanNumberValue = 900 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "D", RomanNumberValue = 500 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "CD", RomanNumberValue = 400 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "C", RomanNumberValue = 100 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "XC", RomanNumberValue = 90 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "L", RomanNumberValue = 50 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "XL", RomanNumberValue = 40 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "X", RomanNumberValue = 10 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "IX", RomanNumberValue = 9 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "V", RomanNumberValue = 5 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "IV", RomanNumberValue = 4 });

        RomanNumbers.Add(new RomanNumberType() { RomanNumber = "I", RomanNumberValue = 1 });
    }

    public string ConvertDecimalNumberToRomanNumber(int GetConvertDecimalNumberToRomanNumber)
    {
        string
            FunctionResult
            , CurrentRomanNumber = "";

        int
            FunctionGet = GetConvertDecimalNumberToRomanNumber
            , DecimalNumberRemaining = FunctionGet;

        foreach(RomanNumberType RomanNumber in RomanNumbers)
            while(RomanNumber.RomanNumberValue <= DecimalNumberRemaining)
            {
                DecimalNumberRemaining -= RomanNumber.RomanNumberValue;

                CurrentRomanNumber += RomanNumber.RomanNumber;
            }

        FunctionResult = CurrentRomanNumber;

        return FunctionResult;
    }
}

usage :

ConvertDecimalNumberToRomanNumberType ConvertDecimalNumberToRomanNumberObject = new ConvertDecimalNumberToRomanNumberType();

ConvertDecimalNumberToRomanNumberObject.Initialize();

var SomeVariable = ConvertDecimalNumberToRomanNumberObject.ConvertDecimalNumberToRomanNumber(1999);
Ternate answered 28/3, 2019 at 20:27 Comment(0)
J
0
First create list of Tuples which contains numbers and corresponds.
Then a method/loops to iterate and return result.

IEnumerable<Tuple<int, string>> data = new List<Tuple<int, string>>()
                {
                  new Tuple<int, string>( 1, "I"),
                  new Tuple<int, string>( 4, "IV" ),
                  new Tuple<int, string>( 5, "V" ),
                  new Tuple<int, string>( 9, "IX" ),
                  new Tuple<int, string>( 10, "X" ),
                  new Tuple<int, string>( 40, "XL" ),
                  new Tuple<int, string>( 50, "L" ),
                  new Tuple<int, string>( 90, "XC" ),
                  new Tuple<int, string>( 100, "C" ),
                  new Tuple<int, string>( 400, "CD" ),
                  new Tuple<int, string>( 500, "D" ),
                  new Tuple<int, string>( 900, "CM"),
                  new Tuple<int, string>( 1000, "M" )
                };


 public string ToConvert(decimal num)
            { 
                 data = data.OrderByDescending(o => o.Item1).ToList(); 
                List<Tuple<int, string>> subData = data.Where(w => w.Item1 <= num).ToList();
                StringBuilder sb = new StringBuilder();
                foreach (var item in subData)
                {
                    if (num >= item.Item1)
                    {
                        while (num >= item.Item1)
                        {
                            num -= item.Item1;
                            sb.Append(item.Item2.ToUpper());
                        }
                    }
                } 
                return sb.ToString();
            }
Jetblack answered 20/4, 2019 at 22:6 Comment(0)
M
0

Here's my effort, built with extension in mind and hopefully easy to understand, probably not the fastest method though. I wanted to complete this as got as part of interview test(which was very demoralising), requires an understanding of the problem first though in order to tackle it.

It should do all numbers, can be checked on here https://www.calculateme.com/roman-numerals/from-roman

        static void Main(string[] args)
    {

        CalculateRomanNumerals(1674);
    }

    private static void CalculateRomanNumerals(int integerInput)
    {
        foreach (var item in Enum.GetValues(typeof(RomanNumerals)).Cast<int>().Reverse())
        {
            integerInput = ProcessNumber(integerInput, item);
        }

        Console.ReadKey();
    }

    private static int ProcessNumber(int input, int number)
    {
        while (input >= number)
        {
            input -= number;
            Console.Write((RomanNumerals)number);
        }

        return input;
    }

    enum RomanNumerals : int
    {
        I = 1,
        IV = 4,
        V = 5,
        IX = 9,
        X = 10,
        L = 50,
        XC = 90,
        C = 100,
        CD = 400,
        D = 500,
        CM = 900,
        M = 1000
    }
Makassar answered 6/5, 2019 at 12:27 Comment(0)
O
0
    # 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

Osbert answered 6/1, 2020 at 22:53 Comment(1)
This post isn't an actual attempt at answering the question. Please note StackOverflow doesn't work like a discussion forum, it is a Q&A site where every post is either a question or an answer to a question. Posts can also have comments - small sentences like this one - that can be used to critique or request clarification from an author. This should be either a comment or a new questionKrimmer
I
0

In javascript

function toRoman(num) {
    var listOfNum = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
    var listOfRoman = ['M', 'CM', 'D', 'CD', "C", 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']

    var numToRoman = '';
    for (let i = 0; i < listOfNum.length; i++) {
        while (num >= listOfNum[i]) {
            numToRoman += listOfRoman[i];
            num -= listOfNum[i];
        }
    }
    return numToRoman;
}

Illnatured answered 20/5, 2020 at 18:55 Comment(0)
D
0
public static string ConvertToRoman(int n)
    {
        StringBuilder romanNumeral = new StringBuilder();

        Dictionary<string, int> romanNumerals = new Dictionary<string, int>();
        romanNumerals.Add("M", 1000);
        romanNumerals.Add("CM", 900);
        romanNumerals.Add("D", 500);
        romanNumerals.Add("CD", 400);
        romanNumerals.Add("C", 100);
        romanNumerals.Add("XC", 90);
        romanNumerals.Add("L", 50);
        romanNumerals.Add("XL", 40);
        romanNumerals.Add("X", 10);
        romanNumerals.Add("IX", 9);
        romanNumerals.Add("V", 5);
        romanNumerals.Add("IV", 4);
        romanNumerals.Add("I", 1);

        int remainder = n;

        foreach (KeyValuePair<string, int> numeral in romanNumerals)
        {
            if (remainder >= numeral.Value)
            {
                int num = (int)(remainder / numeral.Value);
                remainder -= (numeral.Value * num);
                AppendNumerals(romanNumeral, numeral.Key, num);
            }
        }

        return romanNumeral.ToString();
    }

    public static void AppendNumerals(StringBuilder romanNumeral, string numeral, int count)
    {
        for (int i = 0; i < count; i++)
            romanNumeral.Append(numeral);
    }
Dhole answered 23/8, 2022 at 0:38 Comment(0)
A
0

i haven't done exhaustive validation of it, but here's a fully-self-encapsulated awk-based approach that should be correct to encode roman numerals up to 3,999 without needing :: loops, recursion, pre-made reference strings, or lookup arrays ::

 4  function _____(____,_,___,__)
 5  {
 6     return sprintf("%.*s",
 7         int((____+=_=_<_)/(___=(_+=++_)+_^++_)^_),
                "MMM"      )
 8       substr("CDCCCM", ((__=int(____/___^--_)%___)%(___/=_))==_^_
 9         ? ___^(_^-(_=-_)<__) : _+(_=__<___),
10                                             _<-_ ? -_:__%___+!_)
         substr("XLXXXC",
11         ((__=int(____/(___+=___))%___)%(___/=(_+=_^=_<_)))==_^_
12         ? ___^(_^-(_=-_)<__) : _+(_=__<___), _<-_ ?-_:__%___+!_)
13       substr("IVIIIX", ((__=____%(___+___))%___)==(_+=_^=_<_)^_
14         ? ___^(_^-(_=-_)<__) : _+(_=__<___), _<-_ ?-_:__%___+!_)
15  }

 1  8 VIII
 2  54 LIV
 3  65 LXV
 4  90 XC

 5  1999 MCMXCIX
 6  2000 MM
 7  2022 MMXXII
 8  3000 MMM

 9  3499 MMMCDXCIX

Adapting it for most other languages should be rather straight forward.

Acclamation answered 4/11, 2022 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.