Функция std::accumulate
из заголовка <numeric>
— это универсальный инструмент для выполнения операций с диапазоном элементов.
В простейшем виде ею суммируется диапазон чисел:
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
std::cout << "Sum: " << sum << std::endl; // Вывод: «Сумма: 15»
return 0;
}
Здесь 0
— начальное значение, каждый элемент к нему добавляется этой функцией.
Вычисление различных типов
std::accumulate
не ограничивается целыми числами, работает с любым типом, которым поддерживается сложение:
#include <iostream>
#include <vector>
#include <numeric>
#include <string>
int main() {
std::vector<std::string> words = {"Hello", " ", "World", "!"};
std::string result = std::accumulate(words.begin(), words.end(), std::string(""));
std::cout << "Result: " << result << std::endl; // Вывод: «Результат: Hello World!»
return 0;
}
Здесь начальное значение — std::string(“”)
, используемое для конкатенации строк.
Пользовательские бинарные операции
Реальная гибкость std::accumulate
обнаруживается в применении пользовательских бинарных операций:
#include <iostream>
#include <vector>
#include <numeric>
int multiply(int a, int b) {
return a * b;
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int product = std::accumulate(numbers.begin(), numbers.end(), 1, multiply);
std::cout << "Product: " << product << std::endl; // Вывод: «Произведение: 120»
return 0;
}
Здесь accumulate
использована для вычисления произведения всех элементов.
Лямбда-функции с accumulate
С лямбда-функциями код лаконичнее:
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int sum_of_squares = std::accumulate(numbers.begin(), numbers.end(), 0,
[](int total, int num) { return total + num * num; });
std::cout << "Sum of squares: " << sum_of_squares << std::endl; // Вывод: «Сумма квадратов: 55»
return 0;
}
В этом примере вычисляется сумма квадратов всех элементов.
Вычисление со сложными объектами
std::accumulate
работает и с объектами посложнее:
#include <iostream>
#include <vector>
#include <numeric>
#include <string>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people = {
{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}
};
int total_age = std::accumulate(people.begin(), people.end(), 0,
[](int sum, const Person& p) { return sum + p.age; });
std::cout << "Total age: " << total_age << std::endl; // Вывод: «Общий возраст: 90»
return 0;
}
Здесь суммируется возраст всех объектов Person
в векторе.
Реальный сценарий: финансовые расчеты
Воспользуемся std::accumulate
для более практичного сценария — вычисления сложных процентов:
#include <iostream>
#include <vector>
#include <numeric>
#include <cmath>
#include <iomanip>
struct YearlyInterest {
double principal;
double rate;
};
int main() {
std::vector<YearlyInterest> investments = {
{1000, 0.05},
{2000, 0.03},
{1500, 0.04},
{3000, 0.035}
};
double total = std::accumulate(investments.begin(), investments.end(), 0.0,
[](double sum, const YearlyInterest& inv) {
return sum + inv.principal * (1 + inv.rate);
});
std::cout << "Total after one year: $"
<< std::fixed << std::setprecision(2) << total << std::endl;
// Вывод: «Итог после одного года: 7780,00 $»
return 0;
}
В этом примере рассчитывается общее сумма инвестиций после одного года начисления сложных процентов.
accumulate для нечисловых операций
std::accumulate
применяется не только для чисел, им выполняется любая ассоциативная операция:
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
int main() {
std::vector<std::vector<int>> nested = {{1, 2}, {3, 4}, {5, 6}};
std::vector<int> flattened = std::accumulate(nested.begin(), nested.end(),
std::vector<int>{},
[](std::vector<int> acc, const std::vector<int>& vec) {
acc.insert(acc.end(), vec.begin(), vec.end());
return acc;
});
for (int num : flattened) {
std::cout << num << " ";
}
std::cout << std::endl;
// Вывод: 1 2 3 4 5 6
return 0;
}
В этом примере accumulate
используется для сглаживания вложенного вектора.
Параллельное вычисление с C++17
В C++17 появились параллельные алгоритмы, в том числе std::reduce
, который отличается от accumulate
параллельным выполнением:
#include <iostream>
#include <vector>
#include <numeric>
#include <execution>
int main() {
std::vector<int> numbers(10000000, 1); // 10 миллионов 1 сек.
auto sum = std::reduce(std::execution::par, numbers.begin(), numbers.end());
std::cout << "Sum: " << sum << std::endl; // Вывод: «Сумма: 10000000»
return 0;
}
В многоядерных системах этим сильно ускоряется вычисление больших наборов данных.
Производительность
Хотя std::accumulate
универсальна, наиболее эффективным вариантом она является не всегда. Ручным циклом простые операции над базовыми типами выполняются быстрее:
#include <iostream>
#include <vector>
#include <numeric>
#include <chrono>
int main() {
std::vector<int> numbers(10000000, 1); // 10 миллионов 1 сек.
auto start = std::chrono::high_resolution_clock::now();
int sum1 = std::accumulate(numbers.begin(), numbers.end(), 0);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff1 = end - start;
start = std::chrono::high_resolution_clock::now();
int sum2 = 0;
for (int num : numbers) {
sum2 += num;
}
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff2 = end - start;
std::cout << "accumulate time: " << diff1.count() << " seconds\n";
std::cout << "manual loop time: " << diff2.count() << " seconds\n";
return 0;
}
Запустите этот тест у себя в системе и сравните производительность.
accumulate и числа с плавающей точкой
Будьте осторожны, используя accumulate
с числами с плавающей точкой, ибо последовательность операций сказывается на результате из-за ошибок округления:
#include <iostream>
#include <vector>
#include <numeric>
#include <iomanip>
int main() {
std::vector<double> values = {0.1, 0.2, 0.3, 0.4, 0.5};
double sum1 = std::accumulate(values.begin(), values.end(), 0.0);
double sum2 = std::accumulate(values.rbegin(), values.rend(), 0.0);
std::cout << std::setprecision(20);
std::cout << "Forward sum: " << sum1 << std::endl;
std::cout << "Reverse sum: " << sum2 << std::endl;
return 0;
}
Из-за очередности вычислений результаты sum1
и sum2
могут немного отличаться.
Типичные ошибки и как их избежать
- Упускается из виду начальное значение. Всегда указывайте его, для сумм используйте
0
, для произведений —1
. - Некорректный тип начального значения. Убедитесь, что он соответствует предполагаемой операции:
std::vector<double> values = {1.1, 2.2, 3.3};
double sum = std::accumulate(values.begin(), values.end(), 0); // Некорректный: 0 - целое число
double correctSum = std::accumulate(values.begin(), values.end(), 0.0); // Корректный
3. Изменение аккумулятора сумм на месте. При применении пользовательских функций не забывайте, что аккумулятор передается по значению. Меняйте и возвращайте его, не пытайтесь изменить на месте.
4. Упускается из виду возможное переполнение. Для аккумулятора с большими суммами используйте тип побольше:
std::vector<int> largeNumbers(1000000, 1000);
long long sum = std::accumulate(largeNumbers.begin(), largeNumbers.end(), 0LL);
5. Подразумевается ассоциативность, а операции с плавающей точкой не строго ассоциативны. Что касается чисел с плавающей точкой, результаты поточне́е получайте применением std::accumulate
с пользовательской функцией, которой реализуется более надежный алгоритм суммирования.
Заключение
Функция std::accumulate
на C++ не ограничивается простым суммированием. Ею лаконично выполняются самые разные операции над последовательностями данных — от конкатенации строк до вычислений со сложными объектами.
При работе с accumulate
учитывается характер данных и операций. Это отличный инструмент, но оптимален он не всегда и не в любой ситуации.
С простыми операциями над базовыми типами ручной цикл справляется эффективнее. Для больших наборов данных, где приходится кстати параллелизм, применяется std::reduce
.
Читайте также:
- C++: полное руководство по преобразованию строки в число двойной точности
- Механика разрешения имен и связывания на C++
- Создание RESTful API-интерфейсов на C++
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: Detailed Guide on Std::accumulate in C++