accumulate, transform & common <algorithm> patterns
Scenario
You need to multiply every number in a vector by 2, and then sum them up. You write a standard `for` loop with a running total. A senior engineer comments on your PR: "This is imperative. Make it declarative using `<numeric>`."
What is the difference between "imperative" loops and "declarative" algorithms?
Mental model
Raw `for` loops are imperative: you describe *how* to do the work step-by-step. STL algorithms are declarative: you tell the compiler *what* you want done, and it figures out the fastest way to do it.
Beyond sorting and searching, the `<algorithm>` and `<numeric>` headers provide functional programming paradigms. `std::accumulate` folds/reduces an array into a single value. `std::transform` applies a function (like a map) to every element. Using these instead of raw loops makes code more expressive, safer, and open to compiler optimizations.
Explanation
**1. std::accumulate (The Reducer)**
Found in `<numeric>`, it sums up a range. `std::accumulate(vec.begin(), vec.end(), 0)` returns the sum of the vector. The `0` is critical: it sets the initial value AND the return type. If you accumulate floats but pass `0` (an integer) as the start, the result will be truncated to an integer! Pass `0.0` or `0.0f`.
**2. std::transform (The Mapper)**
It iterates through a range, applies a function (usually a lambda) to each element, and writes the result to an output iterator. **Crucial detail:** the destination must either already have enough pre-allocated size, OR you must use `std::back_inserter(dest_vec)` as the output iterator, which safely `.push_back()`s the new items. It's the C++ equivalent of JavaScript's `map()`.
**3. Common Queries**
- `std::count_if`: Counts elements matching a condition.
- `std::any_of` / `std::all_of`: Returns boolean if any/all elements match a condition.
- `std::find_if`: Returns an iterator to the first element matching a condition.
**Real-world Engineering:**
Replacing raw `for` loops with named algorithms is a major tenet of modern C++ (popularized by Sean Parent's "No Raw Loops" talk). A raw `for` loop forces the reader to parse every line of logic to understand the intent. Seeing `std::any_of` instantly communicates: "This block evaluates to a boolean based on a condition."
Code examples
Functional paradigms in C++
#include <vector>
#include <numeric>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 1. Accumulate (Sum)
// The '0' indicates the starting value is an int.
int sum = std::accumulate(vec.begin(), vec.end(), 0);
// 2. Count If
// Count how many numbers are even using a lambda
int evens = std::count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
});
// 3. Any Of
bool hasNegative = std::any_of(vec.begin(), vec.end(), [](int x) {
return x < 0;
});
return 0;
}
These one-liners replace bulky loops and state variables, vastly reducing the surface area for logic errors (like off-by-one indices).
Key points
- No raw loops: Elevate your code from imperative to declarative. Tell the compiler *what* to do using `<algorithm>`.
- Watch the accumulator type: Always ensure the initial value passed to `accumulate` matches the exact type you expect the sum to be.
Common mistakes
- Type truncation in std::accumulate: If you have a `std::vector<double> v = {1.5, 2.5};` and you call `std::accumulate(v.begin(), v.end(), 0)`, the result will be `3` (an int), not `4.0`. The type of the initial value parameter determines the type of the accumulator. You must use `0.0`.
Recall questions
- What is the purpose of the third argument (the starting value) in `std::accumulate`?
- What does `std::transform` do?
- Why is using STL algorithms generally preferred over writing raw `for` loops?
Questions & answers
An interviewer asks you to check if an array of strings contains any empty strings. How would you do this in one line using an STL algorithm?
Using `std::any_of`: `bool hasEmpty = std::any_of(vec.begin(), vec.end(), [](const string& s){ return s.empty(); });`
Approach: Identify the correct algorithm to replace a search/boolean loop.
You are reviewing code that uses a manual `for` loop to convert an entire `std::vector<int>` into a `std::vector<string>` containing the string representations of the numbers. Which `<algorithm>` should they use instead?
They should use `std::transform`. They can pass the source begin/end iterators, a `std::back_inserter` to the destination string vector, and a lambda (or `std::to_string`) as the transformation function.
Approach: Recognize when `transform` (the map operation) is the right tool to convert data types between collections.