Roman numerals to integers

前端 未结 14 897
不思量自难忘°
不思量自难忘° 2020-12-05 20:56

I have a transfer with products that unfortunately has to get matched by product name. The biggest issue here is I might get duplicate products on account of roman numbers.

14条回答
  •  伪装坚强ぢ
    2020-12-05 21:21

    Solution with fulfilling the "subtractive notation" semantics checks

    None of the current solutions completely fulfills the entire set of rules for the "subtractive notation". "IIII" -> is not possible. Each of the solutions results a 4. Also the strings: "CCCC", "VV", "IC", "IM" are invalid.

    A good online-converter to check the semantics is https://www.romannumerals.org/converter So, if you really want doing a completely semantics-check, it's much more complex.

    My approach was to first write the unit tests with the semantic checks. Then to write the code. Then to reduce the loops with some linq expressions.

    Maybe there is a smarter solution, but I think the following code fullfills the rules to convert a roman numerals string.

    After the code section, there is a section with my unit tests.

    public class RomanNumerals
    {
    
        private List> _validNumerals = new List>()
        {
            new Tuple('I', 1, new char? [] {'V', 'X'}),
            new Tuple('V', 5, null),
            new Tuple('X', 10, new char?[] {'L', 'C'}),
            new Tuple('L', 50, null),
            new Tuple('C', 100, new char? [] {'D', 'M'}),
            new Tuple('D', 500, null),
            new Tuple('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;
        }
    }
    

    Here are the UNIT tests

        [TestFixture]
    public class RomanNumeralsTests
    {
        [Test]
        public void TranslateRomanNumeral_WhenArgumentIsNull_RaiseArgumentNullException()
        {
            var romanNumerals = new RomanNumerals();
    
            Assert.Throws(() => romanNumerals.TranslateRomanNumeral(null));
        }
    
        [TestCase("A")]
        [TestCase("-")]
        [TestCase("BXA")]
        [TestCase("MMXK")]
        public void TranslateRomanNumeral_WhenInvalidNumeralSyntax_RaiseException(string input)
        {
            var romanNumerals = new RomanNumerals();
    
            Assert.Throws(() => romanNumerals.TranslateRomanNumeral(input));
        }
    
        [TestCase("IIII")]
        [TestCase("CCCC")]
        [TestCase("VV")]
        [TestCase("IC")]
        [TestCase("IM")]
        [TestCase("XM")]
        [TestCase("IL")]
        [TestCase("MCDXCXI")]
        [TestCase("MCDDXC")]
        public void TranslateRomanNumeral_WhenInvalidNumeralSemantics_RaiseException(string input)
        {
            var romanNumerals = new RomanNumerals();
    
            Assert.Throws(() => romanNumerals.TranslateRomanNumeral(input));
        }
    
    
        [TestCase("I", 1)]
        [TestCase("II", 2)]
        [TestCase("III", 3)]
        [TestCase("IV", 4)]
        [TestCase("XLII", 42)]
        [TestCase("MMXIII", 2013)]
        [TestCase("MXI", 1011)]
        [TestCase("MCDXCIX", 1499)]
        [TestCase("MMXXII", 2022)]
        [TestCase("V", 5)]
        [TestCase("VI", 6)]
        [TestCase("CX", 110)]
        [TestCase("CCCLXXV", 375)]
        [TestCase("MD", 1500)]
        [TestCase("MDLXXV", 1575)]
        [TestCase("MDCL", 1650)]
        [TestCase("MDCCXXV", 1725)]
        [TestCase("MDCCC", 1800)]
        [TestCase("MDCCCLXXV", 1875)]
        [TestCase("MCML", 1950)]
        [TestCase("MMXXV", 2025)]
        [TestCase("MMC", 2100)]
        [TestCase("MMCLXXV", 2175)]
        [TestCase("MMCCL", 2250)]
        [TestCase("MMCCCXXV", 2325)]
        [TestCase("MMCD", 2400)]
        [TestCase("MMCDLXXV", 2475)]
        [TestCase("MMDL", 2550)]
        [TestCase("MMMMMMMM", 8000)]
        [TestCase("MMMMMMMMIV", 8004)]
        public void TranslateRomanNumeral_WhenValidNumeral_Translate(string input, int output)
        {
            var romanNumerals = new RomanNumerals();
    
            var result = romanNumerals.TranslateRomanNumeral(input);
    
            Assert.That(result.Equals(output));
        }
    }
    

提交回复
热议问题