When you are writing C++ code, especially on your interviews, you want to impress the interviewer with the modern C++ features.
Modern C++ introduces several powerful features, including algorithms like std::transform and std::for_each, which leverage functional programming paradigms for concise and expressive code.
For example: the advantages of std::for_each and std::transform:
- Declarative: These functions describe what should happen, rather than how.
- Cleaner code: They simplify loops, making code shorter and more expressive.
- Parallelizable: With C++17 and later, the algorithms can be parallelized by using execution policies like std::execution::par.
Here’s an overview of these functions and their typical use cases.
std::for_each
std::for_each applies a function or callable object to every element in a given range. It is available in the algorithm header.
Syntax:
1 | std::for_each(start, end, function); |
std::for_each(start, end, function);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // Apply a lambda function to each element std::for_each(numbers.begin(), numbers.end(), [](int& num) { num *= 2; // Doubling each number }); // Print the transformed vector for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // Apply a lambda function to each element std::for_each(numbers.begin(), numbers.end(), [](int& num) { num *= 2; // Doubling each number }); // Print the transformed vector for (const int& num : numbers) { std::cout << num << " "; } return 0; }
Output:
2 4 6 8 10
std::transform
std::transform applies a function to a range and stores the result in another range. It can either operate on a single range or on two input ranges (for element-wise transformations).
Syntax:
1 2 3 4 5 6 7 8 9 | // Single range std::transform(start, end, destination, unary_op); // Two ranges std::transform(start1, end1, start2, destination, binary_op); // start1, end1: The first input range. // start2: The second input range (must be at least as long as the first). // destination: The destination range where results are stored. // binary_op: A function that takes two arguments and returns a result. |
// Single range std::transform(start, end, destination, unary_op); // Two ranges std::transform(start1, end1, start2, destination, binary_op); // start1, end1: The first input range. // start2: The second input range (must be at least as long as the first). // destination: The destination range where results are stored. // binary_op: A function that takes two arguments and returns a result.
Example (single range):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; std::vector<int> doubled_numbers(numbers.size()); // Double each element std::transform(numbers.begin(), numbers.end(), doubled_numbers.begin(), [](int num) { return num * 2; }); for (const int& num : doubled_numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; std::vector<int> doubled_numbers(numbers.size()); // Double each element std::transform(numbers.begin(), numbers.end(), doubled_numbers.begin(), [](int num) { return num * 2; }); for (const int& num : doubled_numbers) { std::cout << num << " "; } return 0; }
Output:
2 4 6 8 10
Using std::transform with two ranges allows you to apply a binary operation to corresponding elements from two input ranges and store the result in a third range. This is useful for operations like element-wise addition, multiplication, or comparison.
This example demonstrates how to add corresponding elements of two vectors and store the results in a third vector.
Example: Element-wise addition of two vectors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> vec1 = {1, 2, 3, 4, 5}; std::vector<int> vec2 = {10, 20, 30, 40, 50}; std::vector<int> result(vec1.size()); // Apply element-wise addition using std::transform std::transform(vec1.begin(), vec1.end(), vec2.begin(), result.begin(), [](int a, int b) { return a + b; }); // Print the result vector for (const int& val : result) { std::cout << val << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> vec1 = {1, 2, 3, 4, 5}; std::vector<int> vec2 = {10, 20, 30, 40, 50}; std::vector<int> result(vec1.size()); // Apply element-wise addition using std::transform std::transform(vec1.begin(), vec1.end(), vec2.begin(), result.begin(), [](int a, int b) { return a + b; }); // Print the result vector for (const int& val : result) { std::cout << val << " "; } return 0; }
Output:
11 22 33 44 55
Explanations:
- vec1 and vec2 are the two input vectors.
- result is the vector that will store the results of element-wise addition.
- std::transform takes two input ranges (vec1.begin() and vec2.begin()) and applies the lambda function that adds corresponding elements.
- The resulting values are stored in the result vector.
This technique can be adapted for any binary operation such as subtraction, multiplication, or custom operations like finding the maximum of two elements.
Modern Lambda Support
Both std::for_each and std::transform work seamlessly with C++ lambdas, enabling you to inline small operations without defining separate functions.
std::execution::par (C++17+)
In C++17, the parallel execution policies were introduced. This allows algorithms like std::for_each and std::transform to execute in parallel, improving performance on multi-core systems.
Example with parallel execution:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> #include <vector> #include <algorithm> #include <execution> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // Apply a transformation in parallel std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](int& num) { num *= 2; }); for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> #include <execution> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // Apply a transformation in parallel std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](int& num) { num *= 2; }); for (const int& num : numbers) { std::cout << num << " "; } return 0; }
std::count_if
std::count_if counts the number of elements in a range that satisfy a given predicate. This is useful when you want to know how many elements match certain criteria.
Syntax:
1 | std::count_if(start, end, predicate); |
std::count_if(start, end, predicate);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Count how many numbers are even int count = std::count_if(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; }); std::cout << "Even numbers count: " << count << std::endl; return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Count how many numbers are even int count = std::count_if(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; }); std::cout << "Even numbers count: " << count << std::endl; return 0; }
Output:
1 | Even numbers count: 5 |
Even numbers count: 5
std::find_if
std::find_if searches for the first element in a range that satisfies a given predicate and returns an iterator to it. If no such element is found, it returns the end iterator.
Syntax:
1 | std::find_if(start, end, predicate); |
std::find_if(start, end, predicate);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Find the first number greater than 5 auto it = std::find_if(numbers.begin(), numbers.end(), [](int num) { return num > 5; }); if (it != numbers.end()) { std::cout << "First number greater than 5: " << *it << std::endl; } else { std::cout << "No number greater than 5 found." << std::endl; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Find the first number greater than 5 auto it = std::find_if(numbers.begin(), numbers.end(), [](int num) { return num > 5; }); if (it != numbers.end()) { std::cout << "First number greater than 5: " << *it << std::endl; } else { std::cout << "No number greater than 5 found." << std::endl; } return 0; }
Output:
1 | First number greater than 5: 6 |
First number greater than 5: 6
std::remove_if
std::remove_if reorders the elements in a range, moving elements that satisfy a predicate to the end, and returns an iterator to the new end of the range. It doesn’t actually remove the elements from the container but provides a convenient way to do so when combined with the container’s erase method.
Syntax:
1 | std::remove_if(start, end, predicate); |
std::remove_if(start, end, predicate);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Remove all odd numbers auto new_end = std::remove_if(numbers.begin(), numbers.end(), [](int num) { return num % 2 != 0; }); numbers.erase(new_end, numbers.end()); // Actually remove the elements // Print the modified vector for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Remove all odd numbers auto new_end = std::remove_if(numbers.begin(), numbers.end(), [](int num) { return num % 2 != 0; }); numbers.erase(new_end, numbers.end()); // Actually remove the elements // Print the modified vector for (const int& num : numbers) { std::cout << num << " "; } return 0; }
Output:
1 | 2 4 6 8 10 |
2 4 6 8 10
std::accumulate (from <numeric>)
std::accumulate is used to fold (accumulate) a sequence into a single value, by applying a binary operation (e.g., sum, product) from the left to right.
Syntax:
1 | std::accumulate(start, end, init, binary_op); |
std::accumulate(start, end, init, binary_op);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <iostream> #include <vector> #include <numeric> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // Sum all the numbers int sum = std::accumulate(numbers.begin(), numbers.end(), 0); std::cout << "Sum: " << sum << std::endl; return 0; } |
#include <iostream> #include <vector> #include <numeric> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; // Sum all the numbers int sum = std::accumulate(numbers.begin(), numbers.end(), 0); std::cout << "Sum: " << sum << std::endl; return 0; }
Output:
1 | Sum: 15 |
Sum: 15
std::partition
std::partition reorders the range so that elements satisfying the predicate appear before those that don’t. It returns an iterator to the first element that does not satisfy the predicate.
Syntax:
1 | std::partition(start, end, predicate); |
std::partition(start, end, predicate);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Partition the numbers so that all even numbers come first std::partition(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; }); // Print the partitioned vector for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Partition the numbers so that all even numbers come first std::partition(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; }); // Print the partitioned vector for (const int& num : numbers) { std::cout << num << " "; } return 0; }
Output:
2 4 6 8 10 1 3 5 7 9
std::sort
std::sort sorts a range using a comparator. By default, it sorts in ascending order, but you can specify a custom comparator (e.g., to sort in descending order). It is based on Quicksort algorithm which has in general a O(NLogN) time complexity.
Syntax:
1 2 | std::sort(start, end); std::sort(start, end, comparator); |
std::sort(start, end); std::sort(start, end, comparator);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {5, 3, 9, 1, 6}; // Sort in ascending order std::sort(numbers.begin(), numbers.end()); for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {5, 3, 9, 1, 6}; // Sort in ascending order std::sort(numbers.begin(), numbers.end()); for (const int& num : numbers) { std::cout << num << " "; } return 0; }
Output:
1 3 5 6 9
Using a comparator is straightforward:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {5, 3, 9, 1, 6}; // Sort in ascending order std::sort(numbers.begin(), numbers.end(), [](auto a, auto &b) { return a < b; }); for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {5, 3, 9, 1, 6}; // Sort in ascending order std::sort(numbers.begin(), numbers.end(), [](auto a, auto &b) { return a < b; }); for (const int& num : numbers) { std::cout << num << " "; } return 0; }
std::unique
std::unique removes consecutive duplicate elements from a sorted range, returning an iterator to the new end of the unique elements. This usually works with std::sort before applying std::unique.
Syntax:
1 | std::unique(start, end); |
std::unique(start, end);
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 2, 3, 4, 4, 5}; // Remove consecutive duplicates auto new_end = std::unique(numbers.begin(), numbers.end()); // Erase the duplicates numbers.erase(new_end, numbers.end()); for (const int& num : numbers) { std::cout << num << " "; } return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {1, 2, 2, 3, 4, 4, 5}; // Remove consecutive duplicates auto new_end = std::unique(numbers.begin(), numbers.end()); // Erase the duplicates numbers.erase(new_end, numbers.end()); for (const int& num : numbers) { std::cout << num << " "; } return 0; }
Output:
1 2 3 4 5
std::all_of, std::any_of, std::none_of
These three functions check whether all, any, or none of the elements in a range satisfy a given predicate.
Syntax:
1 2 3 | std::all_of(start, end, predicate); std::any_of(start, end, predicate); std::none_of(start, end, predicate); |
std::all_of(start, end, predicate); std::any_of(start, end, predicate); std::none_of(start, end, predicate);
Example (std::all_of):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {2, 4, 6, 8}; // Check if all numbers are even bool all_even = std::all_of(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; }); std::cout << "All numbers are even: " << (all_even ? "Yes" : "No") << std::endl; return 0; } |
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {2, 4, 6, 8}; // Check if all numbers are even bool all_even = std::all_of(numbers.begin(), numbers.end(), [](int num) { return num % 2 == 0; }); std::cout << "All numbers are even: " << (all_even ? "Yes" : "No") << std::endl; return 0; }
Output:
All numbers are even: Yes
Summary of Modern C++ Algorithms
- Element counting: std::count, std::count_if
- Element finding: std::find, std::find_if
- Transforming sequences: std::for_each, std::transform
- Accumulate: std::accumulate
- Removing elements: std::remove_if, std::unique
- Partitioning: std::partition
- Sorting: std::sort
- Logical checks: std::all_of, std::any_of, std::none_of
These algorithms help make your code more concise, expressive, and maintainable while using C++’s powerful functional-style paradigms.
–EOF (The Ultimate Computing & Technology Blog) —
loading...
Last Post: Comparisions of push_back and emplace_back in C++ std::vector
Next Post: Teaching Kids Programming - Find Cubic Root of an Integer (Brute Force, Binary Search Algorithm) - a^3=54872