Преобразование целых чисел в римские цифры

Я пытаюсь написать функцию, которая преобразует числа в римские цифры. Это мой код до сих пор; однако, он работает только с числами, которые меньше 400. Есть ли быстрый и простой способ сделать это преобразование или расширить существующий код, чтобы он обрабатывал все случаи? Заранее благодарю за любую помощь.

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

32 ответа

Попробуйте это, просто и компактно:

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 ArgumentOutOfRangeException("something bad happened");
}

Я создал этот класс, который делает 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" },
            { 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;
    }
}

Некоторые юнит-тесты для To метод:

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

Некоторые юнит-тесты для From метод:

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

Вот гораздо более простой алгоритм - простите, я не знаю C#, поэтому я пишу это на JavaScript, но тот же алгоритм должен применяться (и я прокомментировал, чтобы вы могли понять алгоритм):

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

Это на самом деле довольно забавная проблема, и, основываясь на обратном примере на dofactory.com (превращение римских цифр в десятичные дроби), довольно легко изменить схему и, возможно, немного улучшить ее. Этот код будет поддерживать цифры от 1 до 3999999.

Начните с класса контекста, это определяет ввод / вывод парсера

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

И абстрактное выражение, которое определяет операцию разбора

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

Теперь вам нужно абстрактное терминальное выражение, которое определяет фактическую операцию, которая будет выполнена:

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

Затем классы, которые определяют поведение римских цифр (обратите внимание, что я использовал соглашение строчных букв, где римские цифры используют черту над буквой, чтобы обозначить 1000 раз)

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

Почти там нам нужно нетерминальное выражение, которое содержит дерево разбора:

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

И наконец, код клиента:

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

Пример из жизни: http://rextester.com/rundotnet?code=JJBYW89744

В 1 строке не очень эффективно, но работает:

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

Надеюсь, самое простое решение, которое вы когда-либо думали:)

public string IntToRoman(int num) {    
    string[] thou={"","M","MM","MMM"};
    string[] hun={"","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"};
    string[] ten={"","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"};
    string[] ones={"","I","II","III","IV","V","VI","VII","VIII","IX"};
    string roman="";        
    roman += thou[(int)(num/1000)%10];
    roman += hun[(int)(num/100)%10];
    roman += ten[(int)(num/10)%10];
    roman += ones[num%10];  

    return roman;
}   

Вот тонкое решение от 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();
}

Хотя мне понравился ответ Мосе Боттачини, использование рекурсии имеет несколько негативных побочных эффектов в этом сценарии. Одним из них является возможное переполнение стека, отсюда и его ограничение верхней границы числа. Хотя да, я понимаю, как нелепо выглядит огромное число в римских цифрах, но это все же ограничение, которое не обязательно для достижения результата.

Кроме того, поскольку строки являются неизменяемыми, его версия будет очень неэффективной из-за интенсивного использования конкатенации строк. Ниже моя модифицированная версия его метода, использующая только цикл while и StringBuilder. Моя версия на самом деле должна быть более производительной (хотя мы говорим о различиях в диапазоне менее миллисекунды) и намного проще в системной памяти.

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

Эта версия не "обманывает", как другие: она генерирует внутреннюю "базовую" таблицу со всеми "базовыми" "составными" числами. Для лени я пользуюсь Tupleс, вместо создания специализированных классов. Если у вас нет C# 4.0, вы можете заменить Tuple<> с KeyValuePair<>, Item1 с Key а также Item2 с 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);
    }
}

Строковое представление соответствующей римской цифры числа.

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

Слишком поздно, возможно, вы уже решили эту проблему, однако этот алгоритм также может помочь вам.

Прежде чем начать, вы можете просто сделать анализ римских литералов. Для известного набора ASCII поддерживаются только значения от 0 до 4000. Если вы хотите выйти за рамки, вы можете определить свой собственный римский буквал тогда.

Прежде чем мы начнем, мы знаем, что с указанным выше диапазоном мы можем сформировать римскую строку из семи вхождений римских литералов (I,V,X,L,C,D и M).

Поэтому мы начнем с простой справочной таблицы, основанной на индексах, которые рассчитываются в другой функции. Неизвестные индексы возвращаются в виде пробела. Как я писал выше, при необходимости можно добавить дополнительные символы:

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

Настоящее преобразование произойдет здесь:

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

Вот и все. Если вы ищете больше ОО Разработанных подходов, пожалуйста, примите ответы выше этого поста. Есть только много способов решить этот подход.

РЕДАКТИРОВАТЬ: Обратите внимание, что это решение не обманывает (просто поиск всех вхождений римских букв), а также:)

Я попробовал, и мое решение выглядит так:

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

Я только что преобразовал решение @jbyrd в C#. Вот:

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

Я могу предоставить метод, который сравнительно проще, чем существующий

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

Еще одно прямое решение. Пытаясь немного улучшить производительность, я использую StringBuilder, перебирая меньше ключей (один другой сайт, конечно, LINQ, где может добавить дополнительную задержку)

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

Аккуратное, быстрое и простое решение

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

Решение с выполнением проверок семантики "вычитающей нотации"

Ни одно из существующих решений полностью не соответствует всему набору правил для "вычитающей нотации". "IIII" -> невозможно. Каждое из решений дает результат 4. Также недопустимы строки: "CCCC", "VV", "IC", "IM".

Хороший онлайн-конвертер для проверки семантики - https://www.romannumerals.org/converter Итак, если вы действительно хотите провести полную проверку семантики, это намного сложнее.

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

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

Решение длинное, но простое для начинающего. Процесс до 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;
        }
    }
}
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;

}

Мысль эта проблема была интересной, это был мой взгляд на это.

Надо (надеюсь) иметь дело с числами до верхнего предела зачеркнутых символов. Добавление любых других соглашений должно быть просто вопросом настройки новых полос и настройки ConfigureNext цепь.

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

Тесты:

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

    }

Было бы проще, если бы вы перевернули римские цифры на случай, подобный XIV. Код ссылки из этого блога.

Вот версия из @Cammilius, конвертированная в C# - у меня работает на малых числах, и это все, что мне нужно для моего варианта использования.

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

    for (var i = 0; i < decimalValue.Length; i = i + 1)
    { //itarate through array of decimal values
        //iterate more to find values not explicitly provided in the decimalValue array
        while (decimalValue[i] <= num_cp)
        {
            result = result + romanNumeral[i];
            num_cp = num_cp - decimalValue[i];
        }
    }
    return result;
}
    # checks if given roman number is valid, empty means 0
Function IsRoman {

    [OutputType([Boolean])]
    Param([String] $roman)

    return ($roman -ne $Null) -and ($roman -match ("^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"));
}

# checks if given arabic number is valid, less than 0 or floating point is not possible and means error
Function IsConvertibleToRoman {

    [OutputType([Boolean])]
    Param([String] $arabic)

    Try {
        $result = $arabic -match "^\-?(\d+\.?\d*)(e\-?\d+)?$" -and [Double]$arabic -eq [Long]$arabic -and [Long]$arabic -ge 0;
    }
    Catch {
        # this extra check is necessary because conversion to double fails with hex numbers
        return $arabic -match "^0x[0-9a-f]+$";
    }

    return $result;
}

# convert arabic numerals into roman numerals, including validity check, allow three ciphres as maximum
Function ArabicToRoman {

    [OutputType([String])]
    Param([String] $arabicNumber)

    If (-not (IsConvertibleToRoman $arabicNumber)) {
        [String]$errorMessage = "can not convert '$arabicNumber' into roman number!";

        Write-Error $errorMessage -Category InvalidArgument;

        return $Null;
    }

    [Long]$number = $arabicNumber;

    # ---------------------------------------------------------------
    [String]$romanNumber = "M"*([math]::Floor($number / 1000));
    # ---------------------------------------------------------------
    $number = $number % 1000;

    If ($number -ge 500) {
        If ($number -ge 900) {
            $romanNumber = $romanNumber + "CM";
            $number = $number - 900;
        } Else {
            $romanNumber = $romanNumber + "D";
            $number = $number - 500;
        }
    }

    If ($number -lt 400) {
        $romanNumber = $romanNumber + "C"*([math]::Floor($number / 100));
    } Else {
        $romanNumber = $romanNumber + "CD";
    }
    # ---------------------------------------------------------------
    $number = $number % 100;

    If ($number -ge 50) {
        If ($number -ge 90) {
            $romanNumber = $romanNumber + "XC";
            $number = $number - 90;
        } Else {
            $romanNumber = $romanNumber + "L";
            $number = $number - 50;
        }
    }

    If ($number -lt 40) {
        $romanNumber = $romanNumber + "X"*([math]::Floor($number / 10));
    } Else {
        $romanNumber = $romanNumber + "XL";
    }
    # ---------------------------------------------------------------
    $number = $number % 10;

    If ($number -ge 5) {
        If ($number -eq 9) {
            $romanNumber = $romanNumber + "IX";
            return $romanNumber;
        } Else {
            $romanNumber = $romanNumber + "V";
            $number = $number - 5;
        }
    }

    If ($number -lt 4) {
        $romanNumber = $romanNumber + "I"*$number;
    } Else {
        $romanNumber = $romanNumber + "IV";
    }
    # ---------------------------------------------------------------

    return $romanNumber;
}

# convert roman numerals into arabic numerals, including validity check, evaluation from left to right
Function RomanToArabic {

    [OutputType([Int])]
    Param([String] $romanNumber)

    [long]$arab = 0;
    [char]$lastCipher = $Null;

    If (-not (isRoman $romanNumber)) {
        [String]$errorMessage = "'$romanNumber' is not a roman numeral!";

        Write-Error $errorMessage -Category InvalidArgument

        return -1;
    }

    Foreach($aCipher In $romanNumber.ToUpper().ToCharArray()) {
        Switch ($aCipher) {
            'I' {
                $arab += 1;
                Break;
            }
            'V' {
                If ($lastCipher -eq 'I') {
                    $arab += 4 - 1;
                } Else {
                    $arab += 5;
                }
                Break;
            }
            'X' {
                If ($lastCipher -eq 'I') {
                    $arab += 9 - 1;
                } Else {
                    $arab += 10;
                }
                Break;
            }
            'L' {
                If ($lastCipher -eq 'X') {
                    $arab += 40 - 10;
                } Else {
                    $arab += 50;
                }
                Break;
            }
            'C' {
                If ($lastCipher -eq 'X') {
                    $arab += 90 - 10;
                } Else {
                    $arab += 100;
                }
                Break;
            }
            'D' {
                If ($lastCipher -eq 'C') {
                    $arab += 400 - 100;
                } Else {
                    $arab += 500;
                }
                Break;
            }
            'M' {
                If ($lastCipher -eq 'C') {
                    $arab += 900 - 100;
                } Else {
                    $arab += 1000;
                }
                Break;
            }
        }

        $lastCipher = $aCipher;
    }

    return $arab;
}

Пожалуйста, посмотрите здесь для получения дополнительной информации:

https://github.com/CBM6502/Conversion-Test

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

Вот мои усилия, построенные с учетом расширений и, надеюсь, легко понятные, хотя, вероятно, не самый быстрый метод. Я хотел завершить это, так как получил его как часть собеседования (которое было очень деморализующим), сначала требуется понимание проблемы, хотя для ее решения.

Он должен делать все цифры, можно проверить здесь 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
    }

@Backwards_Dave Вы хотели решение, которое идет на максимальное число, вот и вы:

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

использование:

ConvertDecimalNumberToRomanNumberType ConvertDecimalNumberToRomanNumberObject = new ConvertDecimalNumberToRomanNumberType();

ConvertDecimalNumberToRomanNumberObject.Initialize();

var SomeVariable = ConvertDecimalNumberToRomanNumberObject.ConvertDecimalNumberToRomanNumber(1999);

Я нахожу код BrunoLM очень простым и элегантным, но функция From(...) действительно должна проверить, является ли источник действительной римской цифрой.
Что-то вроде этого

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;
    }
Другие вопросы по тегам