开发者

Converting integers to roman numerals

开发者 https://www.devze.com 2023-03-28 04:53 出处:网络
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 co

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开发者_如何学C.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();           
    }


Try this, simple and compact:

public static string ToRoman(int number)
{
    if ((number < 0) || (number > 3999)) throw new ArgumentOutOfRangeException("insert value betwheen 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");
}


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


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


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


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


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


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


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


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 :)


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


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


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


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


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


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


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


 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.


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


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


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


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


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


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;

}


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


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


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


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
    }


    # 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


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

0

精彩评论

暂无评论...
验证码 换一张
取 消