Дана строка, содержащая цифры и некоторые латинские буквы. Нужно найти сумму всех цифр. Рассмотрим несколько решений.
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.
В 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
Добавим шаблоны для типа суммы и типа контейнера.
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 (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::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)), их можно использовать для работы с разными видами строк и контейнеров, поддерживающих соответствующие итераторы.
Приложение: исходный код.