Преобразование между римскими и арабскими цифрами в Java

Преобразование между римскими и арабскими цифрами в Java

1. Вступление

Древние римляне разработали свою собственную систему счисления, называемую римскими цифрами. Система использует буквы с разными значениями для представления чисел. Римские цифры до сих пор используются в некоторых небольших приложениях.

В этом руководстве мы реализуемsimple converters that will transform numbers from one system to the other.

2. Римские цифры

В римской системе мы имеем7 symbols that represent numbers:

  • I представляет 1

  • V представляет 5

  • X представляет 10

  • L представляет 50

  • C представляет 100

  • D представляет 500

  • M представляет 1000

Первоначально люди представляли 4 с IIII или 40 с XXXX. Это может быть довольно неудобно читать. Также легко принять четыре символа рядом друг с другом за три символа.

Roman numerals use subtractive notation, чтобы избежать таких ошибок. Вместоfour times one (IIII) можно сказать, что этоone less than five (IV).

Насколько это важно с нашей точки зрения? Это важно, потому что вместо простого добавления чисел символ за символом нам может потребоваться проверить следующий символ, чтобы определить, следует ли добавлять или вычитать число.

3. модель

Давайте определим перечисление для представления римских цифр:

enum RomanNumeral {
    I(1), IV(4), V(5), IX(9), X(10),
    XL(40), L(50), XC(90), C(100),
    CD(400), D(500), CM(900), M(1000);

    private int value;

    RomanNumeral(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public static List getReverseSortedValues() {
        return Arrays.stream(values())
          .sorted(Comparator.comparing((RomanNumeral e) -> e.value).reversed())
          .collect(Collectors.toList());
    }
}

Обратите внимание, что мы определили дополнительные символы, чтобы облегчить вычитающую запись. Мы также определили дополнительный метод с именемgetReverseSortedValues().

Этот метод позволит нам явно получить определенные римские цифры в порядке убывания значения.

4. С римского на арабский

Roman numerals can only represent integers between 1 to 4000. Мы можем использовать следующий алгоритм для преобразования римской цифры в арабское число (перебор символов в обратном порядке отM доI):

LET numeral be the input String representing an Roman Numeral
LET symbol be initialy set to RomanNumeral.values()[0]
WHILE numeral.length > 0:
    IF numeral starts with symbol's name:
        add symbol's value to the result
        remove the symbol's name from the numeral's beginning
    ELSE:
        set symbol to the next symbol

4.1. Реализация

Далее мы можем реализовать алгоритм на Java:

public static int romanToArabic(String input) {
    String romanNumeral = input.toUpperCase();
    int result = 0;

    List romanNumerals = RomanNumeral.getReverseSortedValues();

    int i = 0;

    while ((romanNumeral.length() > 0) && (i < romanNumerals.size())) {
        RomanNumeral symbol = romanNumerals.get(i);
        if (romanNumeral.startsWith(symbol.name())) {
            result += symbol.getValue();
            romanNumeral = romanNumeral.substring(symbol.name().length());
        } else {
            i++;
        }
    }

    if (romanNumeral.length() > 0) {
        throw new IllegalArgumentException(input + " cannot be converted to a Roman Numeral");
    }

    return result;
}

4.2. Test

Наконец, мы можем проверить реализацию:

@Test
public void given2018Roman_WhenConvertingToArabic_ThenReturn2018() {
    String roman2018 = "MMXVIII";

    int result = RomanArabicConverter.romanToArabic(roman2018);

    assertThat(result).isEqualTo(2018);
}

5. С арабского на римский

Мы можем использовать следующий алгоритм для преобразования арабских цифр в римские (перебор символов в обратном порядке отM доI):

LET number be an integer between 1 and 4000
LET symbol be RomanNumeral.values()[0]
LET result be an empty String
WHILE number > 0:
    IF symbol's value <= number:
        append the result with the symbol's name
        subtract symbol's value from number
    ELSE:
        pick the next symbol

5.1. Реализация

Далее мы можем теперь реализовать алгоритм:

public static String arabicToRoman(int number) {
    if ((number <= 0) || (number > 4000)) {
        throw new IllegalArgumentException(number + " is not in range (0,4000]");
    }

    List romanNumerals = RomanNumeral.getReverseSortedValues();

    int i = 0;
    StringBuilder sb = new StringBuilder();

    while ((number > 0) && (i < romanNumerals.size())) {
        RomanNumeral currentSymbol = romanNumerals.get(i);
        if (currentSymbol.getValue() <= number) {
            sb.append(currentSymbol.name());
            number -= currentSymbol.getValue();
        } else {
            i++;
        }
    }

    return sb.toString();
}

5.2. Test

Наконец, мы можем проверить реализацию:

@Test
public void given1999Arabic_WhenConvertingToRoman_ThenReturnMCMXCIX() {
    int arabic1999 = 1999;

    String result = RomanArabicConverter.arabicToRoman(arabic1999);

    assertThat(result).isEqualTo("MCMXCIX");
}

6. Заключение

В этой быстрой статье мы показали, как преобразовывать римские цифры в арабские.

Мы использовалиenum для представления набора римских цифр и создали служебный класс для выполнения преобразований.

Полную реализацию и все тесты можно найти вover on GitHub.