Converting integers to roman numerals

前端 未结 29 2295
走了就别回头了
走了就别回头了 2020-12-02 09:16

I\'m trying to write a function that converts numbers to roman numerals. This is my code so far; however, it only works with numbers that are less than 400. Is there a quick

相关标签:
29条回答
  • 2020-12-02 09:32

    In 1 line, not very efficient but works:

    public string RomanNumeralFrom(int number)
    {
        return
            new string('I', number)
                .Replace(new string('I', 1000), "M")
                .Replace(new string('I', 900), "CM")
                .Replace(new string('I', 500), "D")
                .Replace(new string('I', 400), "CD")
                .Replace(new string('I', 100), "C")
                .Replace(new string('I', 90), "XC")
                .Replace(new string('I', 50), "L")
                .Replace(new string('I', 40), "XL")
                .Replace(new string('I', 10), "X")
                .Replace(new string('I', 9), "IX")
                .Replace(new string('I', 5), "V")
                .Replace(new string('I', 4), "IV");
    }
    
    0 讨论(0)
  • 2020-12-02 09:32

    This version doesn't "cheat" as others: it generates internally the "base" table with all the "base" "composable" numbers. For lazyness I'm using Tuples, instead of creating specialized classes. If you don't have C# 4.0, you can replace Tuple<> with KeyValuePair<>, Item1 with Key and Item2 with Value.

    static Tuple<IList<Tuple<string, int>>, int> GenerateBaseNumbers()
    {
        const string letters = "IVXLCDM";
    
        var tuples = new List<Tuple<string, int>>();
        Tuple<string, int> subtractor = null;
    
        int num = 1;
        int maxNumber = 0;
    
        for (int i = 0; i < letters.Length; i++)
        {
            string currentLetter = letters[i].ToString();
    
            if (subtractor != null)
            {
                tuples.Add(Tuple.Create(subtractor.Item1 + currentLetter, num - subtractor.Item2));
            }
    
            tuples.Add(Tuple.Create(currentLetter, num));
    
            bool isEven = i % 2 == 0;
    
            if (isEven)
            {
                subtractor = tuples[tuples.Count - 1];
            }
    
            maxNumber += isEven ? num * 3 : num;
            num *= isEven ? 5 : 2;
        }
    
        return Tuple.Create((IList<Tuple<string, int>>)new ReadOnlyCollection<Tuple<string, int>>(tuples), maxNumber);
    }
    
    static readonly Tuple<IList<Tuple<string, int>>, int> RomanBaseNumbers = GenerateBaseNumbers();
    
    static string FromNumberToRoman(int num)
    {
        if (num <= 0 || num > RomanBaseNumbers.Item2)
        {
            throw new ArgumentOutOfRangeException();
        }
    
        StringBuilder sb = new StringBuilder();
    
        int i = RomanBaseNumbers.Item1.Count - 1;
    
        while (i >= 0)
        {
            var current = RomanBaseNumbers.Item1[i];
    
            if (num >= current.Item2)
            {
                sb.Append(current.Item1);
                num -= current.Item2;
            }
            else
            {
                i--;
            }
        }
    
        return sb.ToString();
    }
    
    static void Main(string[] args)
    {
        for (int i = 1; i <= RomanBaseNumbers.Item2; i++)
        {
            var calc = FromNumberToRoman(i);
    
            Console.WriteLine("{1}", i, calc);
        }
    }
    
    0 讨论(0)
  • 2020-12-02 09:33

    I've created this class that does decimal <=> roman

    public static class Roman
    {
        public static readonly Dictionary<char, int> RomanNumberDictionary;
        public static readonly Dictionary<int, string> NumberRomanDictionary;
    
        static Roman()
        {
            RomanNumberDictionary = new Dictionary<char, int>
            {
                { 'I', 1 },
                { 'V', 5 },
                { 'X', 10 },
                { 'L', 50 },
                { 'C', 100 },
                { 'D', 500 },
                { 'M', 1000 },
            };
    
            NumberRomanDictionary = new Dictionary<int, string>
            {
                { 1000, "M" },
                { 900, "CM" },
                { 500, "D" },
                { 400, "CD" },
                { 100, "C" },
                { 90, "XC" },
                { 50, "L" },
                { 40, "XL" },
                { 10, "X" },
                { 9, "IX" },
                { 5, "V" },
                { 4, "IV" },
                { 1, "I" },
            };
        }
    
        public static string To(int number)
        {
            var roman = new StringBuilder();
    
            foreach (var item in NumberRomanDictionary)
            {
                while (number >= item.Key)
                {
                    roman.Append(item.Value);
                    number -= item.Key;
                }
            }
    
            return roman.ToString();
        }
    
        public static int From(string roman)
        {
            int total = 0;
    
            int current, previous = 0;
            char currentRoman, previousRoman = '\0';
    
            for (int i = 0; i < roman.Length; i++)
            {
                currentRoman = roman[i];
    
                previous = previousRoman != '\0' ? RomanNumberDictionary[previousRoman] : '\0';
                current = RomanNumberDictionary[currentRoman];
    
                if (previous != 0 && current > previous)
                {
                    total = total - (2 * previous) + current;
                }
                else
                {
                    total += current;
                }
    
                previousRoman = currentRoman;
            }
    
            return total;
        }
    }
    

    Some Unit Tests for To method:

    [TestClass]
    public class DecimalToRomanTest
    {
        [TestMethod]
        public void Roman_1_I()
        {
            Assert.AreEqual("I", Roman.To(1));
        }
    
        [TestMethod]
        public void Roman_2_II()
        {
            Assert.AreEqual("II", Roman.To(2));
        }
    
        [TestMethod]
        public void Roman_3_III()
        {
            Assert.AreEqual("III", Roman.To(3));
        }
    
        [TestMethod]
        public void Roman_4_IV()
        {
            Assert.AreEqual("IV", Roman.To(4));
        }
    
        [TestMethod]
        public void Roman_5_V()
        {
            Assert.AreEqual("V", Roman.To(5));
        }
    
        [TestMethod]
        public void Roman_9_IX()
        {
            Assert.AreEqual("IX", Roman.To(9));
        }
    
        [TestMethod]
        public void Roman_10_X()
        {
            Assert.AreEqual("X", Roman.To(10));
        }
    
        [TestMethod]
        public void Roman_49_XLIX()
        {
            Assert.AreEqual("XLIX", Roman.To(49));
        }
    
        [TestMethod]
        public void Roman_50_L()
        {
            Assert.AreEqual("L", Roman.To(50));
        }
    
        [TestMethod]
        public void Roman_100_C()
        {
            Assert.AreEqual("C", Roman.To(100));
        }
    
        [TestMethod]
        public void Roman_400_CD()
        {
            Assert.AreEqual("CD", Roman.To(400));
        }
    
        [TestMethod]
        public void Roman_500_D()
        {
            Assert.AreEqual("D", Roman.To(500));
        }
    
        [TestMethod]
        public void Roman_900_CM()
        {
            Assert.AreEqual("CM", Roman.To(900));
        }
    
        [TestMethod]
        public void Roman_1000_M()
        {
            Assert.AreEqual("M", Roman.To(1000));
        }
    
        [TestMethod]
        public void Roman_11984_MMMMMMMMMMMCMLXXXIV()
        {
            Assert.AreEqual("MMMMMMMMMMMCMLXXXIV", Roman.To(11984));
        }
    }
    

    Some Unit Tests for From method:

    [TestClass]
    public class RomanToDecimalTest
    {
        [TestMethod]
        public void Roman_I_1()
        {
            Assert.AreEqual(1, Roman.From("I"));
        }
    
        [TestMethod]
        public void Roman_II_2()
        {
            Assert.AreEqual(2, Roman.From("II"));
        }
    
        [TestMethod]
        public void Roman_III_3()
        {
            Assert.AreEqual(3, Roman.From("III"));
        }
    
        [TestMethod]
        public void Roman_IV_4()
        {
            Assert.AreEqual(4, Roman.From("IV"));
        }
    
        [TestMethod]
        public void Roman_V_5()
        {
            Assert.AreEqual(5, Roman.From("V"));
        }
    
        [TestMethod]
        public void Roman_IX_9()
        {
            Assert.AreEqual(9, Roman.From("IX"));
        }
    
        [TestMethod]
        public void Roman_X_10()
        {
            Assert.AreEqual(10, Roman.From("X"));
        }
    
        [TestMethod]
        public void Roman_XLIX_49()
        {
            Assert.AreEqual(49, Roman.From("XLIX"));
        }
    
        [TestMethod]
        public void Roman_L_50()
        {
            Assert.AreEqual(50, Roman.From("L"));
        }
    
        [TestMethod]
        public void Roman_C_100()
        {
            Assert.AreEqual(100, Roman.From("C"));
        }
    
        [TestMethod]
        public void Roman_CD_400()
        {
            Assert.AreEqual(400, Roman.From("CD"));
        }
    
        [TestMethod]
        public void Roman_D_500()
        {
            Assert.AreEqual(500, Roman.From("D"));
        }
    
        [TestMethod]
        public void Roman_CM_900()
        {
            Assert.AreEqual(900, Roman.From("CM"));
        }
    
        [TestMethod]
        public void Roman_M_1000()
        {
            Assert.AreEqual(1000, Roman.From("M"));
        }
    
        [TestMethod]
        public void Roman_MMMMMMMMMMMCMLXXXIV_11984()
        {
            Assert.AreEqual(11984, Roman.From("MMMMMMMMMMMCMLXXXIV"));
        }
    }
    
    0 讨论(0)
  • 2020-12-02 09:33

    I find BrunoLM's code very simple and elegant but the From(...) function really needs to check if the source is a valid roman numeral.
    Something like this

    public static bool IsValidRomanNumber(string source) {
            bool result = true;
    
            string[] invalidCouples = { "VV", "LL", "DD", "VX", "VC", "VM", "LC", "LM", "DM", "IC", "IM", "XM" };
    
            foreach (string s in invalidCouples) {
                if (source.Contains(s)) {
                    result = false;
                    break;
                }
            }
    
            return result;
        }
    
    0 讨论(0)
  • 2020-12-02 09:33

    Here is the version from @Cammilius converted to C# - works for me on low numbers which is all I need for my usecase.

    public String convertToRoman(int num)
    {    
        //Roman numerals to have <= 3 consecutive characters, the distances between deciaml values conform to this
        int[] decimalValue = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 };
        string[] romanNumeral = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" };
        int num_cp = num; // copy the function parameter into num_cp
        String result = "";
    
        for (var i = 0; i < decimalValue.Length; i = i + 1)
        { //itarate through array of decimal values
            //iterate more to find values not explicitly provided in the decimalValue array
            while (decimalValue[i] <= num_cp)
            {
                result = result + romanNumeral[i];
                num_cp = num_cp - decimalValue[i];
            }
        }
        return result;
    }
    
    0 讨论(0)
  • 2020-12-02 09:33
        # checks if given roman number is valid, empty means 0
    Function IsRoman {
    
        [OutputType([Boolean])]
        Param([String] $roman)
    
        return ($roman -ne $Null) -and ($roman -match ("^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"));
    }
    
    # checks if given arabic number is valid, less than 0 or floating point is not possible and means error
    Function IsConvertibleToRoman {
    
        [OutputType([Boolean])]
        Param([String] $arabic)
    
        Try {
            $result = $arabic -match "^\-?(\d+\.?\d*)(e\-?\d+)?$" -and [Double]$arabic -eq [Long]$arabic -and [Long]$arabic -ge 0;
        }
        Catch {
            # this extra check is necessary because conversion to double fails with hex numbers
            return $arabic -match "^0x[0-9a-f]+$";
        }
    
        return $result;
    }
    
    # convert arabic numerals into roman numerals, including validity check, allow three ciphres as maximum
    Function ArabicToRoman {
    
        [OutputType([String])]
        Param([String] $arabicNumber)
    
        If (-not (IsConvertibleToRoman $arabicNumber)) {
            [String]$errorMessage = "can not convert '$arabicNumber' into roman number!";
    
            Write-Error $errorMessage -Category InvalidArgument;
    
            return $Null;
        }
    
        [Long]$number = $arabicNumber;
    
        # ---------------------------------------------------------------
        [String]$romanNumber = "M"*([math]::Floor($number / 1000));
        # ---------------------------------------------------------------
        $number = $number % 1000;
    
        If ($number -ge 500) {
            If ($number -ge 900) {
                $romanNumber = $romanNumber + "CM";
                $number = $number - 900;
            } Else {
                $romanNumber = $romanNumber + "D";
                $number = $number - 500;
            }
        }
    
        If ($number -lt 400) {
            $romanNumber = $romanNumber + "C"*([math]::Floor($number / 100));
        } Else {
            $romanNumber = $romanNumber + "CD";
        }
        # ---------------------------------------------------------------
        $number = $number % 100;
    
        If ($number -ge 50) {
            If ($number -ge 90) {
                $romanNumber = $romanNumber + "XC";
                $number = $number - 90;
            } Else {
                $romanNumber = $romanNumber + "L";
                $number = $number - 50;
            }
        }
    
        If ($number -lt 40) {
            $romanNumber = $romanNumber + "X"*([math]::Floor($number / 10));
        } Else {
            $romanNumber = $romanNumber + "XL";
        }
        # ---------------------------------------------------------------
        $number = $number % 10;
    
        If ($number -ge 5) {
            If ($number -eq 9) {
                $romanNumber = $romanNumber + "IX";
                return $romanNumber;
            } Else {
                $romanNumber = $romanNumber + "V";
                $number = $number - 5;
            }
        }
    
        If ($number -lt 4) {
            $romanNumber = $romanNumber + "I"*$number;
        } Else {
            $romanNumber = $romanNumber + "IV";
        }
        # ---------------------------------------------------------------
    
        return $romanNumber;
    }
    
    # convert roman numerals into arabic numerals, including validity check, evaluation from left to right
    Function RomanToArabic {
    
        [OutputType([Int])]
        Param([String] $romanNumber)
    
        [long]$arab = 0;
        [char]$lastCipher = $Null;
    
        If (-not (isRoman $romanNumber)) {
            [String]$errorMessage = "'$romanNumber' is not a roman numeral!";
    
            Write-Error $errorMessage -Category InvalidArgument
    
            return -1;
        }
    
        Foreach($aCipher In $romanNumber.ToUpper().ToCharArray()) {
            Switch ($aCipher) {
                'I' {
                    $arab += 1;
                    Break;
                }
                'V' {
                    If ($lastCipher -eq 'I') {
                        $arab += 4 - 1;
                    } Else {
                        $arab += 5;
                    }
                    Break;
                }
                'X' {
                    If ($lastCipher -eq 'I') {
                        $arab += 9 - 1;
                    } Else {
                        $arab += 10;
                    }
                    Break;
                }
                'L' {
                    If ($lastCipher -eq 'X') {
                        $arab += 40 - 10;
                    } Else {
                        $arab += 50;
                    }
                    Break;
                }
                'C' {
                    If ($lastCipher -eq 'X') {
                        $arab += 90 - 10;
                    } Else {
                        $arab += 100;
                    }
                    Break;
                }
                'D' {
                    If ($lastCipher -eq 'C') {
                        $arab += 400 - 100;
                    } Else {
                        $arab += 500;
                    }
                    Break;
                }
                'M' {
                    If ($lastCipher -eq 'C') {
                        $arab += 900 - 100;
                    } Else {
                        $arab += 1000;
                    }
                    Break;
                }
            }
    
            $lastCipher = $aCipher;
        }
    
        return $arab;
    }
    

    Please have a look here for further information:

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

    0 讨论(0)
提交回复
热议问题