【问题标题】:Converting integers to roman numerals将整数转换为罗马数字
【发布时间】:2011-10-25 19:11:22
【问题描述】:

我正在尝试编写一个将数字转换为罗马数字的函数。到目前为止,这是我的代码;但是,它只适用于小于 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();           
    }

【问题讨论】:

标签: c# c#-3.0 roman-numerals


【解决方案1】:

试试这个,简单又紧凑:

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

【讨论】:

  • 为什么递归不好?我认为这是一个很好的解决方案
  • 只要有充分的理由,递归就不是“坏的”。它确实有一些缺点。例如,请注意上面代码中的上限 3999。如果您删除此限制并在此方法中抛出大量数字,您可以很快得到堆栈溢出(不是双关语)。而且,虽然我喜欢这种设计的简单性,但由于大量的字符串连接,从内存的角度来看它并不是很有效。
  • @MikeU 此方法为它重复出现的每一层返回至少 1 个字符。充其量,它的递归深度将是返回的最终字符串的长度。你会得到一个 StackOverflowException 是值得怀疑的,如果你这样做了,最终的罗马数字将太长而无法使用。 (对于更大的数字,您只需在给定字符上划一条线来表示该字符的 1000 倍)
  • @CaptainCrypto 你没看错。该解决方案绝对是聪明的,但我看不出使用递归来完成可以通过“while”循环和字符串生成器轻松完成的事情的意义。这不仅消除了堆栈问题,而且还稍微提高了性能和内存效率,因为您可以使用 StringBuilder 而不是连接字符串,并且您没有所有无关的方法调用。
  • 但是......这里的堆栈深度是O(log(n))。此外,罗马数字最多可以使用几位数字,也许是 8?与 StringBuilder 的差异甚至可以测量吗?有了这个推理,函数式编程应该被禁止,一切都应该在汇编程序中实现,因为只有这样你才能充分利用机器的力量。这可能会导致荒谬。
【解决方案2】:

我创建了这个类来执行decimal &lt;=&gt; roman

public static class Roman
{
    public static readonly Dictionary<char, int> RomanNumberDictionary;
    public static readonly Dictionary<int, string> NumberRomanDictionary;

    static Roman()
    {
        RomanNumberDictionary = new Dictionary<char, int>
        {
            { 'I', 1 },
            { 'V', 5 },
            { 'X', 10 },
            { 'L', 50 },
            { 'C', 100 },
            { 'D', 500 },
            { 'M', 1000 },
        };

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

    public static string To(int number)
    {
        var roman = new StringBuilder();

        foreach (var item in NumberRomanDictionary)
        {
            while (number >= item.Key)
            {
                roman.Append(item.Value);
                number -= item.Key;
            }
        }

        return roman.ToString();
    }

    public static int From(string roman)
    {
        int total = 0;

        int current, previous = 0;
        char currentRoman, previousRoman = '\0';

        for (int i = 0; i < roman.Length; i++)
        {
            currentRoman = roman[i];

            previous = previousRoman != '\0' ? RomanNumberDictionary[previousRoman] : '\0';
            current = RomanNumberDictionary[currentRoman];

            if (previous != 0 && current > previous)
            {
                total = total - (2 * previous) + current;
            }
            else
            {
                total += current;
            }

            previousRoman = currentRoman;
        }

        return total;
    }
}

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

【讨论】:

  • 注意:正如@a​​bc667 提到的。NumberRomanDictionary 字典缺少一个键值 [90 : "XC"] !!!
  • 字典的顺序不是不确定的吗?如果是这样,如果“我”先出来会怎样?那不是一直打印 I 直到它达到零吗?此外,您不会检查是否有负面影响。
【解决方案3】:

这是一个更简单的算法 - 原谅我,我不懂 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

【讨论】:

  • 一个functional 接近return int.toString().split("").reverse().reduce((acc, x, i) =&gt; (i &lt; 3 ? romanNumerals[i][x] : "M".repeat(x)) + acc, "");。适用于 1-9999。
  • 如果你想要这个的 C# 版本,我刚刚在下面的答案中提交了它。我会把它放在这里,但是评论太长了。
【解决方案4】:

这实际上是一个非常有趣的问题,并且基于dofactory.com 上的反向示例(将罗马数字转换为小数)很容易反转模式,并且可能会改进一点。此代码将支持从 1 到 3999999 的数字。

从上下文类开始,它定义了解析器的 I/O

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

【讨论】:

  • 为什么代码限制为最大输入 3999999?让它工作到 Int32.MaxValue 有多难?
  • 为什么会出错?你可以有一个相同长度的疯狂长的十进制数字,并不意味着它是错误的。罗马数字没有最大数字,类似于十进制,你可以将数字串在一起,组成你想要的任何整数。
  • @DavidKlempfner 那个 url 是错误的或者你没有看到完整的值。
  • @Leandro 仅仅因为您看不到完整的值并不意味着它不存在。我想知道为什么 Jamiec 认为你不能写超过 3999999 的罗马数字。
  • @DavidKlempfner 我认为就这个答案而言,这完全无关紧要。如果您想扩展此代码以支持更大的数字,它应该非常简单 - 读者练习。
【解决方案5】:

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

【讨论】:

  • 聪明——我喜欢这种方法。 :)
【解决方案6】:

这应该是最简单的解决方案了。

public string IntToRoman(int num)
{
    var result = string.Empty;
    var map = new Dictionary<string, int>
    {
        {"M", 1000 },
        {"CM", 900},
        {"D", 500},
        {"CD", 400},
        {"C", 100},
        {"XC", 90},
        {"L", 50},
        {"XL", 40},
        {"X", 10},
        {"IX", 9},
        {"V", 5},
        {"IV", 4},
        {"I", 1}
    };
    foreach (var pair in map)
    {
        result += string.Join(string.Empty, Enumerable.Repeat(pair.Key, num / pair.Value));
        num %= pair.Value;
    }
    return result;
}

【讨论】:

  • 为什么将千位列限制为 3000?
  • @DavidKlempfner 以前的解决方案有一个限制。我已将其更新为通用的。
  • 逻辑很简单,但您可能最好将 string.Join 和字符串连接替换为字符串生成器。这样会更有效率,但会保持简单
【解决方案7】:

此版本不像其他版本那样“作弊”:它在内部生成包含所有“基本”“可组合”数字的“基本”表。为了懒惰,我使用Tuples,而不是创建专门的类。如果您没有 C# 4.0,则可以将 Tuple&lt;&gt; 替换为 KeyValuePair&lt;&gt;,将 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);
    }
}

【讨论】:

  • +1 是一个很好的解决方案,它不会因为到处都有诸如“XC”之类的硬编码值而作弊。干得好!
【解决方案8】:

虽然我喜欢 Mosè Bottacini 的回答,但在这种情况下使用递归会产生一些负面影响。一个是可能的堆栈溢出,因此他限制了数字的上限。虽然,是的,我意识到一个巨大的数字在罗马数字中看起来多么荒谬,但这仍然是一个限制,不是实现结果所必需的。

另外,由于字符串是不可变的,由于大量使用字符串连接,他的版本的内存效率将非常低。下面是他的方法的修改版本,只使用了一个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();
}

【讨论】:

  • 在快速性能测试中,我将这两种方法都计数到了 3999(因为这是 Mosè Bottacini 方法的上限)。递归方法在 3 毫秒内以罗马数字计算 0-3999。我的在 2 毫秒内完成了。所以,是的,性能差异不大。
  • 限制为 3999 的原因是因为在 3999 之后您使用顶栏表示法。顶部栏 = 1,000 条,顶部和底部栏 = 1,000,000 条。
【解决方案9】:

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

【讨论】:

    【解决方案10】:

    太晚了,可能你已经解决了这个问题,但是这个算法也可以为你解决问题。

    在开始之前,您可以简单地对罗马文字进行分析。对于已知的 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;
        }
    

    就是这样。如果您寻找更多 OO 设计的方法,请接受本文上方的答案。解决这种方法的方法有很多。

    编辑: 请注意,此解决方案也不会作弊(只是查找所有出现的罗马文字):)

    【讨论】:

    • @JackGriffin 我的意思是三个罗马文字,因此不允许使用四个“C”。
    【解决方案11】:

    数字对应的罗马数字的字符串表示形式。

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

    【讨论】:

      【解决方案12】:

      我可以提供一种比现有方法更简单的方法

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

      【讨论】:

        【解决方案13】:

        我试了一下,我的解决方案如下所示:

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

        【讨论】:

          【解决方案14】:

          简洁、快速和直接的解决方案

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

          【讨论】:

          • 我注意到您对 CM、CD、XC 等进行了硬编码。您在脑海中使用了什么逻辑来计算出 900 是 CM?难道你不能使用你在你的头脑中,在你的程序中使用的逻辑来减少罗马数字符号的重复,从而拥有集中的控制点吗?如果要将“C”更改为“c”,则必须在很多地方进行
          【解决方案15】:

          更直接的解决方案。尝试稍微提高我使用 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();
              }
          }
          

          【讨论】:

            【解决方案16】:

            实现“减法”语义检查的解决方案

            当前的解决方案都没有完全满足“减法”的整套规则。 “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;
                }
            }
            

            【讨论】:

              【解决方案17】:

              我刚刚将@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;
                  }
              }
              

              【讨论】:

                【解决方案18】:
                 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 这样的情况会更容易。代码来自blog

                【讨论】:

                  【解决方案19】:
                  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;
                          }
                      }
                  }
                  

                  【讨论】:

                    【解决方案20】:

                    我发现 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;
                        }
                    

                    【讨论】:

                      【解决方案21】:
                              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();    
                      

                      【讨论】:

                        【解决方案22】:

                        解决方案很长,但对于初学者来说很容易理解。处理多达 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;
                                }
                            }
                        }
                        

                        【讨论】:

                          【解决方案23】:

                          这是从@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;
                          }
                          

                          【讨论】:

                          • 我在寻找一些工作代码时根本没有考虑它。我只发布了@Camilius 的 C# 版本的答案。如果我在脑海中想一想,我可能会决定硬编码的值可以生成紧凑、可理解和可维护的代码。替换硬编码的 if-then 逻辑不会是,恕我直言。
                          • 紧凑且易于理解,但是它缺乏将超过 3999999 作为输入的能力。没有规定你不能连续超过 3 个连续的相同字母。此规则也不存在于十进制值中。
                          • 唯一的规则是,如果您可以将较小的字母放在较大的字母前面,则必须这样做,其结果是大多数情况下这会阻止 3 个连续的相同字母,但是这当您处理最大的罗马数字字母时不适用,因为没有更高的字母可以放在前面。
                          【解决方案24】:
                          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;
                          
                          }
                          

                          【讨论】:

                            【解决方案25】:

                            觉得这个问题很有趣,这就是我的看法。

                            应该(希望)处理不超过划线字符上限的数字。添加任何其他约定应该只是配置新频段和调整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);
                                }
                            }
                            

                            【讨论】:

                              【解决方案26】:

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

                              【讨论】:

                                【解决方案27】:
                                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();
                                            }
                                

                                【讨论】:

                                  【解决方案28】:

                                  这是我的努力,在构建时考虑了扩展,希望易于理解,但可能不是最快的方法。我想完成这个作为面试测试的一部分(这非常令人沮丧),但需要先了解问题才能解决它。

                                  它应该做所有的数字,可以在这里检查 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
                                      }
                                  

                                  【讨论】:

                                    【解决方案29】:
                                        # 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

                                    【讨论】:

                                    【解决方案30】:

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

                                    【讨论】:

                                      猜你喜欢
                                      • 2019-01-09
                                      • 2012-10-09
                                      • 1970-01-01
                                      • 2020-05-11
                                      • 2021-12-15
                                      • 2014-10-19
                                      • 2014-03-04
                                      相关资源
                                      最近更新 更多