Ежедневный бит (е) C ++, Общая проблема интервью C ++: английские числа

Сегодня мы рассмотрим распространенную задачу на собеседовании в C++: английские числа.

Учитывая целое число без знака, преобразуйте его в текстовое представление на английском языке.

Прежде чем продолжить чтение решения, попробуйте решить его самостоятельно. Вот ссылка на Compiler Explorer с парой тестов: https://compiler-explorer.com/z/vY3463hKd.

Решение

В этой задаче нет никаких хитростей или сложных алгоритмов. Единственная цель этой задачи — проверить усердие.

Таким образом, стоит сначала записать полную спецификацию.

Преобразование является периодическим, с периодом 1000, ненулевые периоды формируют вывод в виде «X Magnitude», например. «42 миллиона». X — это преобразование значения 0–999 в текстовую форму.

Числа 0–99 имеют полурегулярный формат:

  • 0..14 неправильные: «Ноль», «Один», «Два», «Три», «Четыре», «Пять», «Шесть», «Семь», «Восемь», «Девять», «Десять» , «Одиннадцать», «Двенадцать», «Тринадцать», «Четырнадцать».
  • 15..19 и 20–99 являются обычными, с использованием общих префиксов: «Twen», «Thir», «For», «Fif», «Six», «Seven», «Eight» и «Nine».
  • 15..19 префикс основан на второй цифре, за которой следует «teen».
  • 20..99, префикс основан на первой цифре, за которой следует «ty». Вторая цифра основана на нерегулярном диапазоне 0..9 (без нуля).

Мы можем преобразовать это непосредственно в код:

constexpr std::string_view prefixes[] = {
    "", "", "Twen", "Thir", "For", "Fif", "Six", 
    "Seven", "Eigh", "Nine"
};
constexpr std::string_view tenths[] = {
    "Zero", "One", "Two", "Three", "Four", 
    "Five", "Six", "Seven", "Eight", "Nine",
    "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen"
};

// 0..99
struct Tenths {
    unsigned value;
    friend std::ostream& operator<<(std::ostream& s, Tenths v) {
        // 0..14
        if (v.value <= 14)
            return s << tenths[v.value];
        // 15..19 (e.g. Fif-teen)
        if (v.value <= 19)
            return s << prefixes[v.value%10] << "teen";
        // First digit of 20..99 (e.g. Eigh-ty)
        s << prefixes[v.value/10] << "ty";
        // Second digit of 20..99 (e.g. For-ty Two)
        if (v.value%10 != 0)
            s << " " << Tenths{v.value%10};
        return s;
    }
};

Сотни представлены однозначным числом, основанным на нерегулярном диапазоне от 0 до 9 (полностью опуская ноль).

// 0..999
struct Hundreds {
    unsigned value;
    friend std::ostream& operator<<(std::ostream& s, Hundreds v) {
        // Skip over Zero
        if (v.value == 0) return s;
        std::string delim = "";
        // Number of hundreds, omit if zero
        if (v.value >= 100)
            s << std::exchange(delim, " ") << 
                 tenths[v.value / 100] << " Hundred";
        // 0..99, only add delimiter if we also 
        // have hundreds, e.g. One Hundred Forty Two
        if (v.value % 100 != 0)
            s << delim << Tenths{v.value % 100};
        return s;
    }
};

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

constexpr std::string_view ranks[] = {
    "", " Thousand", " Million", " Billion", " Trillions"
};

std::string toEnglish(unsigned number) {
 // Zero
    if (number == 0)
        return std::string(tenths[0]);
    
    std::string result;
    unsigned rank = 0;
    while (number != 0) {
        unsigned rem = number % 1000; // current 0..999
        // Skip over zeroes
        if (rem != 0) {
            std::stringstream s;
            // Format the 0..999 and append the rank
            s << Hundreds{rem} << ranks[rank];
            // If we already have some formatted text 
            // add it with a delimiter.
            if (!result.empty())
                s << " " << result;
            result = s.str();
        }
        ++rank;
        number /= 1000;
    }
    return result;
}

Откройте решение в Compiler Explorer.