private static Exception?CheckRepeatedDigits(string roman, RomanizerOptions options) { var allowFourSum = options.HasFlag(RomanizerOptions.ALLOW_FOUR_SEQUENTIAL_DIGITS); var allowMultipleSum = options.HasFlag(RomanizerOptions.ALLOW_MULTIPLE_SEQUENTIAL_DIGITS); for (var i = 0; i < roman.Length; i++) { var current = roman[i]; var totalRepeated = (byte)roman.Skip(i).TakeWhile(digit => digit == current).Count(); switch (totalRepeated) { case <= 3: continue; case 4 when !allowFourSum: return(new ArithmeticAdditionException( roman, current, totalRepeated, $"Roman number '{roman}' with multiple repeated digits of '{current}' with total '{totalRepeated}', so it is invalid roman number. If you want sum 4 digits you need allow this in options entry.")); case > 4 when !allowMultipleSum: return(new ArithmeticAdditionException( roman, current, totalRepeated, $"Roman number '{roman}' with multiple repeated digits of '{current}' with total '{totalRepeated}', so it is invalid roman number. If you want sum more than 4 digits you need allow this in options entry.")); } } return(null); }
internal static ushort HumanizeUnsafe(string roman, RomanizerOptions options) { ushort total = 0, index = 0; do { var number = roman[index]; var left = Util.LeftOrNullUnsafe(roman, index); var right = Util.RightOrNullUnsafe(roman, index); var currentValue = Util.ConvertSingleRomanToDecimal(number); var rightDecimal = CheckRightDecimalSpecialRules(options, left, right, number); index++; var hasRightValue = rightDecimal > 0; var needSubtractFromRightDecimal = hasRightValue && rightDecimal > currentValue; if (needSubtractFromRightDecimal) { total += (ushort)(rightDecimal - currentValue); index++; continue; } total += currentValue; } while (index < roman.Length); return(total); }
public static bool IsValidRoman(string?roman, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { if (roman is null) { return(false); } if (string.IsNullOrEmpty(roman.Trim().ToUpper())) { return(false); } var hasInvalidRomanDigit = roman.All(digit => Constants.ROMAN_NUMBERS.ContainsKey(digit)) is false; if (hasInvalidRomanDigit) { return(false); } var hasRepeatedDigits = CheckRepeatedDigits(roman.Trim().ToUpper(), options) is not null; if (hasRepeatedDigits) { return(false); } return(true); }
/// <summary> /// Validate a roman number /// </summary> /// <param name="roman">Roman number input</param> /// <param name="options">Options to parse this roman number</param> /// <returns>Valid roman number</returns> /// <exception cref="ArgumentNullException">Roman number can not be a null string</exception> /// <exception cref="ArgumentException">Invalid roman digit</exception> /// <exception cref="ArithmeticAdditionException">Parse not allowed by rules options</exception> public static string RomanNumber(string?roman, RomanizerOptions options) { if (string.IsNullOrEmpty(roman)) { throw new ArgumentNullException(nameof(roman)); } string cleanRoman = roman.Trim().ToUpper(); if (string.IsNullOrEmpty(cleanRoman)) { throw new ArgumentNullException(nameof(roman)); } var hasInvalidRomanDigit = roman.All(digit => Constants.ROMAN_NUMBERS.ContainsKey(digit)) is false; if (hasInvalidRomanDigit) { throw new ArgumentException($"Invalid roman char: {roman}"); } var exception = CheckRepeatedDigits(cleanRoman, options); var hasRepeatedDigits = exception is not null; if (hasRepeatedDigits) { throw exception !; } return(cleanRoman); }
internal static string RomanizeUnsafe(ushort number, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { StringBuilder roman = new(MAX_VALUE_ROMAN.Length); do { for (var i = 0; i < romanNumbersReversed.Count(); i++) { var(romanDigit, decimalDigit) = romanNumbersReversed.ElementAt(i); if (number < decimalDigit) { continue; } var count = (ushort)Math.Round((double)(number / decimalDigit)); /* * Special rule * * When the number have free 4 units, need subtract then by higher value for not throw 4 sum limit. * * Example: IIII => IV */ var isSpecialCountRule = count == 4 && !options.HasFlag(RomanizerOptions.ALLOW_FOUR_SEQUENTIAL_DIGITS); if (isSpecialCountRule) { var higherValueIndex = i - 1; var(rightRomanDigit, rightDigit) = romanNumbersReversed.ElementAtOrDefault(higherValueIndex); var existRightValue = rightRomanDigit != 0 && rightDigit > 0; /* * Need check if have higher value to subtract free units */ if (existRightValue) { roman.Append(romanDigit); roman.Append(rightRomanDigit); number -= (ushort)(decimalDigit * count); break; } } /* * Special rule end */ for (var j = 0; j < count; j++) { roman.Append(romanDigit); } number -= (ushort)(decimalDigit * count); break; } } while (number > 0); return(roman.ToString()); }
private static ushort CheckRightDecimalSpecialRules(RomanizerOptions options, char?left, char?right, char number) { if (right is not null && right.HasValue) { var rightDecimal = Util.ConvertSingleRomanToDecimal(right.Value); var needCheckSpecialRules = left.HasValue && right.HasValue; if (needCheckSpecialRules) { CheckSpecialRules(left !.Value, right !.Value, number, rightDecimal, options); } return(rightDecimal); } return(0); }
private static void CheckSpecialRules(char left, char right, char current, int rightValue, RomanizerOptions options) { var isNotStrict = !options.HasFlag(RomanizerOptions.DEFAULT_STRICT); if (isNotStrict) { return; } var isAddition = Util.ConvertSingleRomanToDecimal(current) >= rightValue; if (isAddition) { CheckAdditionSpecialRules(current, rightValue, options); } else { CheckSubtractionSpecialRules(left, right, current, options); } }
public void InvalidRomanToHumanTest(string roman, Type @throw, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { Assert.Throws(@throw, () => Romano.Humanize(roman, options) as object); // Assert.False(Romano.IsValidRoman(roman)); }
public static char?RightOrNull(string roman, int index, RomanizerOptions options) { return(RightOrNullUnsafe(Validate.RomanNumber(roman, options), index)); }
private static void CheckSubtractionSpecialRules(char left, char right, char current, RomanizerOptions options) { if (JUST_ONE_CAN_SUBSTRACT.Contains(right) && current != 'I') { throw new ArithmeticSubtractionException(left, right, $"Invalid left '{left}' right '{right}' with strict rule (default) enabled you cant subtract '{right}' by '{left}'"); } if (JUST_TEN_CAN_SUBSTRACT.Contains(right) && current != 'X') { throw new ArithmeticSubtractionException(left, right, $"Invalid left '{left}' right '{right}' with strict rule (default) enabled you cant subtract '{right}' by '{left}'"); } }
private static void CheckAdditionSpecialRules(char current, int rightValue, RomanizerOptions options) { }
public static ushort Humanize(string roman, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { return(HumanizeUnsafe(Validate.RomanNumber(roman, options), options)); }
public static string Romanize(ushort number, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { var validNumber = Validate.HumanNumber(number); return(RomanizeUnsafe(validNumber, options)); }
public static string Romanize(sbyte number, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { return(Romanize((ushort)number, options)); }
public static ushort Humanize(char roman, RomanizerOptions options = RomanizerOptions.DEFAULT_STRICT) { return(Humanize(roman.ToString(), options)); }