Converting integers to roman numerals

2019-01-08 09:34发布

问题:

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? Thanks in advance for any help.

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

回答1:

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


回答2:

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


回答3:

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


回答4:

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



回答5:

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


回答6:

Hopefully the simplest solution you have ever thought :)

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


回答7:

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


回答8:

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


回答9:

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



回答10:

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


回答11:

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


回答12:

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


回答13:

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


回答14:

 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.



回答15:

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


回答16:

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


回答17:

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


回答18:

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


回答19:

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


回答20:

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


回答21:

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;

}


回答22:

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