Dyzzet|
C++ Data Science Алгоритмы Темы · Блог · YouTube
std::accumulate, std::reduce и std::transform_reduce

Дана строка, содержащая цифры и некоторые латинские буквы. Нужно найти сумму всех цифр. Рассмотрим несколько решений.

unsigned sumOnlyDigits(const std::string& data)
{
    unsigned sum{};
    for (auto c : data)
    {
        if (isNumber(c))
        {
            sum += c - '0';
        }
    }
    return sum;
}

Код вполне прозрачный, он перебирает символы и находит сумму всех цифр с учётом того, что код '0' — 48, код '1' — 49, код '2' — 50 и т. д. То есть выражение c - '0' даёт числа в диапазоне 0...9 вместо 48...57.

std::accumulate

В STL есть функция std::accumulate из заголовочного файла <numeric>.

unsigned sumOnlyDigits(const std::string& str) noexcept
{
    return std::accumulate(str.cbegin(), str.cend(), 0u,
        [](unsigned partialSum, auto symbol) noexcept {
            return std::isdigit(symbol)
                ? (partialSum + static_cast<unsigned>(symbol - '0')) : partialSum;
        });
}

Аргументы data.cbegin() и data.cend() означают, что нужно рассмотреть всю строку, третий аргумент — нейтральный элемент, то есть начальная нулевая сумма.

Четвёртый аргумент — лямбда-функция. Её собственные аргументы — частичная сумма, накопленная до рассматриваемого символа, и сам очередной символ. Если символ является цифрой (std::isdigit() из <cctype>), то возвращается новая сумма, включающая эту цифру, иначе — просто старая сумма.

std::string str{ "12345AB678" };
auto result{ sumOnlyDigits(str) }; // 36

Добавим шаблоны для типа суммы и типа контейнера.

Недостаточно передавать третьим аргументом просто 0, запись static_cast<T>(0) нужна, чтобы избежать ненужных неявных преобразований.

template <typename T = unsigned, typename C>
T sumOnlyDigits(const C& container) noexcept
{
    return std::accumulate(container.cbegin(), container.cend(), static_cast<T>(0),
        [](T partialSum, auto symbol) noexcept {
            return std::isdigit(symbol)
                ? (partialSum + static_cast<T>(symbol - '0')) : partialSum;
        });
}

Теперь можно легко поменять тип суммы, по умолчанию это unsigned.

std::string str{ "12345AB678" };
auto result{ sumOnlyDigits<int>(str) }; // 36

Или использовать строки с широкими символами.

std::wstring str{ L"12345AB678" };
auto result{ sumOnlyDigits(str) }; // 36

А также любые коллекции символов.

std::vector vec{ '1', '2', '3', '4', '5', 'A', 'B', '6', '7', '8' };
auto result{ sumOnlyDigits(vec) }; // 36

std::reduce

Практически то же самое делает функция std::reduce (C++17), но без гарантий порядка вычислений.

template <typename T = unsigned, typename C>
T sumOnlyDigits(const C& container) noexcept
{
    return std::reduce(container.cbegin(), container.cend(), static_cast<T>(0),
        [](T partialSum, auto symbol) noexcept {
            return std::isdigit(symbol)
                ? (partialSum + static_cast<T>(symbol - '0')) : partialSum;
        });
}

std::transform_reduce

Функция std::transform_reduce выполняет необходимое действие (в данном случае сложение — std::plus{}) с начальным нейтральным элементом и всеми элементами контейнера, преобразованными по заданному правилу (без гарантий порядка). Правило задано лямбда-функцией: все элементы, отличные от цифр, становятся нулями.

template <typename T = unsigned, typename C>
T sumOnlyDigits(const C& container) noexcept
{
    return std::transform_reduce(container.cbegin(), container.cend(), static_cast<T>(0),
        std::plus{},
        [](auto symbol) noexcept {
            return static_cast<T>(std::isdigit(symbol) ? symbol - '0' : 0);
        });
}
'1' '2' '3' '4' '5' 'A' 'B' '6' '7' '8'
 ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓
 1 + 2 + 3 + 4 + 5 + 0 + 0 + 6 + 7 + 8

Такие варианты реализации более явно показывают намерения программиста, пригодны для распараллеливания (паралельные версии требуют дополнительный аргумент и могут выбрасывать исключения, так что потребуется изменить спецификатор noexcept на noexcept(false)), их можно использовать для работы с разными видами строк и контейнеров, поддерживающих соответствующие итераторы.

Приложение: исходный код.

14 августа 2022
Зарегистрируйтесь и войдите, чтобы оставлять комментарии и голосовать.

Также может быть интересным
© MMXI—MMXXIII. RSS. Поддержать сайт
Светлая тема / тёмная тема