# 8 Key Standard Library Algorithms

An introduction to 8 more useful algorithms from the standard library, and how we can use them alongside views, projections, and other techniques
This lesson is part of theÂ course:

### Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
###### Ryan McCombe
Updated

The next chapter of the course introduces over 50 algorithms from the standard library. We can use these to solve a wide range of problems that we are likely to encounter in ourÂ projects.

Before we go in deep, itâ€™s worth highlighting some of the particularly useful algorithms. Here, we introduce eight algorithms that are very commonlyÂ needed.

To reinforce our knowledge, we will also weave in techniques from previous lessons, including views, projections, iterators, and subranges. Remember, these and similar techniques can be applied to any algorithm in the standard library, not just the ones weâ€™re using in thisÂ lesson.

## 1. The for_each() Algorithm

The std::ranges::for_each() algorithm accepts a range as its first argument and a function as its second. The function is then invoked for every element in the range, receiving that element as anÂ argument.

The following example uses for_each() to log every value in aÂ collection:

#include <vector>
#include <iostream>
#include <algorithm>

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(Numbers, [](int x){
std::cout << x << ", ";
});
}
1, 2, 3, 4, 5,

In many cases, the for_each() algorithm can replace other forms of iteration, such as a range-based forÂ loop.

This is especially true when the function we want to invoke already exists, allowing our for_each() call to be both succinct andÂ descriptive:

#include <vector>
#include <iostream>
#include <algorithm>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(Numbers, Log);
}
1, 2, 3, 4, 5,

In these examples, we copy the values into the function we pass to for_each(). But, we can avoid this by passing them as a const reference, or a non-const reference if we want to modify the values inÂ place.

Below, we double all of the values in ourÂ collection:

#include <vector>
#include <iostream>
#include <algorithm>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(Numbers, [](int& x){
x *= 2;
});

std::ranges::for_each(Numbers, Log);
}
2, 4, 6, 8, 10,

As a reminder, for_each() and most of the other standard library algorithms we cover in this lesson and the next chapter can interact with projection functions, views, iterators, and subranges. This gives us a lot more flexibility - we can combine these techniques as required to solve our specific useÂ cases.

### Projection Functions

A projection function receives each element of our collection as an argument, and can then return a different value. It is this value - the projection - that will then be used within theÂ algorithm.

The following example uses a projection function to project each value in our collection to double its value. It is then these projections that are sent to our Log()Â function:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int Double(int Number){ return Number * 2; }

int main(){
std::vector Numbers{1, 2, 3, 4, 5};
std::ranges::for_each(Numbers, Log, Double);
}
2, 4, 6, 8, 10,

Itâ€™s worth remembering that this is not doubling the values in our Numbers function. The Log() function is called with projections of the values in our collection. These projections are based on the original values but are different objects. The values in our original Numbers collection are unchanged by the previousÂ code.

Additionally, the value returned by the projection function need not be the same type as the argument it receives. Below, our projection function receives a custom Character object, and returns a std::string based on that Character:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(std::string Name) {/*...*/};

class Character {/*...*/};

std::string GetName(Character& Character){
return Character.Name;
}

int main(){
using namespace std::string_literals;
std::vector<Character> Characters{
"Anna"s, "Roderick"s, "Bob"s};

std::ranges::for_each(Characters, Log,
GetName);
}
Anna, Roderick, Bob,

### Views

Our algorithms can work on views. This gives us the option to change our data at the collection level before handing it to our algorithm. The following example logs our objects in reverse order using the std::views::reverseÂ view:

#include <algorithm>
#include <ranges>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(
std::views::reverse(Numbers), Log);
}
5, 4, 3, 2, 1,

We can also use a view to restrict the algorithm to run only on a subset of our data. In this example, we use the std::views::filter view to only have the algorithm act on the objects in our collection that areÂ odd:

#include <algorithm>
#include <iostream>
#include <vector>
#include <ranges>

void Log(int Number) {/*...*/};

bool isOdd(int Num){ return Num % 2 == 1; }

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(
std::views::filter(Numbers, isOdd), Log);
}
1, 3, 5,

Views can be composed together using the | operator, giving us even more options. Below, we run our algorithm on only the odd numbers that are within the initial 3 objects in ourÂ collection:

#include <algorithm>
#include <iostream>
#include <vector>
#include <ranges>

void Log(int Number) {/*...*/};

bool isOdd(int Num){ return Num % 2 == 1; }

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(
std::views::take(Numbers, 3) |
std::views::filter(isOdd) |
std::views::reverse,
Log);
}
3, 1,

### Iterator-Sentinel Pairs

All of the algorithms we cover in this lesson, and the next chapter, allow us to define our ranges using iterator-sentinel pairs. Below, we use this to pass only a subset of our collection to the for_each()Â algorithm:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(Numbers.begin() + 1,
Numbers.end() - 1, Log);
}
2, 3, 4,

### Subranges

We can combine an iterator pair into a subrange using the std::ranges::subrange utility. Below, we create a subrange comprising the middle three elements of ourÂ collection:

#include <algorithm>
#include <vector>

int main(){
std::vector Numbers{1, 2, 3, 4, 5};
auto Subrange{
std::ranges::subrange(Numbers.begin() + 1,
Numbers.end() - 1)};
}

A subrange is a view and can be used like any other view, and composed with other views in the usual ways. Below, we compose our subrange with std::views::reverse, and pass the result of that expression to the for_each() algorithm alongside our Log()Â function.

This causes the middle three elements of our collection to be logged in reverseÂ order:

#include <algorithm>
#include <iostream>
#include <vector>
#include <ranges>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};

std::ranges::for_each(
std::ranges::subrange(Numbers.begin() + 1,
Numbers.end() - 1) |
std::views::reverse,
Log);
}
4, 3, 2,

We wonâ€™t always call these techniques out when covering future algorithms, but they always apply. We can use views, iterators, and projections as needed to get our desiredÂ behavior.

## 2. The transform() Algorithm

The std::ranges::transform() function populates a range by passing values from some other range into a function. The return value of each successive function call is then used to populate the targetÂ range.

The most basic usage of the transform() function accepts threeÂ arguments:

1. The input range
2. An iterator pointing to where the outputs should be placed. We need to ensure there is enough memory allocated at this location to accommodate our results.
3. The transformer - a function that implements the transformation

In this example, our Output vector is populated with double the values of our InputÂ vector:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Input{1, 2, 3, 4, 5};
std::vector<int> Output;

// Ensure there is enough memory allocated
// to hold the output of the algorithm
Output.resize(Input.size());

std::ranges::transform(Input, Output.begin(),
[](int x){
return x * 2;
});

std::ranges::for_each(Output, Log);
}
2, 4, 6, 8, 10,

The output iterator can be the same as the beginning of the input range, allowing us to transform our objects inÂ place.

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Input{1, 2, 3, 4, 5};

std::ranges::transform(Input, Input.begin(),
[](int x){
return x * 2;
});

std::ranges::for_each(Input, Log);
}
2, 4, 6, 8, 10,

### Algorithm Return Values and std::ranges::in_out_result

The transform() algorithm, and many of the other algorithms weâ€™ll encounter in this lesson and in the next chapter, have a return value. This return value tends to be useful for follow-upÂ operations.

Algorithms that accept one input range and one output range typically return a std::ranges::in_out_result. This is often aliased to other names based on the algorithm weâ€™re using. For example, the previous transform() examples alias this return type as std::ranges::unary_transform_result.

However, the underlying type is the same across various algorithms. std::ranges::in_out_result is a struct with twoÂ fields:

• in - An iterator pointing to where the input range ended. If the end of our range is denoted by an iterator, in will be equal to that iterator. If the end of our range is denoted by any other type of sentinel, in will be the iterator that triggered that sentinel.
• out - An iterator that is past the last element inserted into the output range

These are usually accessed by structured binding. We show some examples of how they can be used for follow-up operationsÂ below:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Input{1, 2, 3, 4, 5};
std::vector<int> Output;

Output.resize(8);

auto [in, out] = std::ranges::transform(
Input, Output.begin(),
[](int x){ return x * 2; });

std::cout << "Output: ";
std::ranges::for_each(Output, Log);

std::cout << "\nObjects Transformed: " <<
std::distance(Output.begin(), out);

std::cout << "\nLast Object: " << *(out - 1);

Output.erase(out, Output.end());
std::cout << "\nTrimmed Output: ";
std::ranges::for_each(Output, Log);
}
Output: 2, 4, 6, 8, 10, 0, 0, 0,
Objects Transformed: 5
Last Object: 10
Trimmed Output: 2, 4, 6, 8, 10,

### Binary Transform

The transform() algorithm is also overloaded to accept two input ranges. Our transformer function is then invoked with the objects in the same position of both inputs. The return values of those function calls are then used to populate ourÂ output.

Below, we populate our output by adding the numbers from two inputÂ ranges.

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Input1{1, 2, 3, 4, 5};
std::vector Input2{2, 3, 4, 5, 6};
std::vector<int> Output;
Output.resize(Input1.size());

std::ranges::transform(
Input1, Input2, Output.begin(),
[](int x, int y){ return x + y; });

std::ranges::for_each(Output, Log);
}

For example, the first object in Input1 is 1, the first object in Input2 is 2, and our transformer function adds the two arguments together. As such, the first object in Output will be 3.

This process continues until we have combined all of ourÂ inputs:

3, 5, 7, 9, 11,

### Standard Library Operators

Within the <functional> header, the standard library includes templated function objects for most standardÂ operators.

We can use these to replace lambdas that are similar to what we had in the previous example. We could alternatively have written it likeÂ this:

std::ranges::transform(Input1, Input2,
Output.begin(),
std::plus{}
);

### std::ranges::in_in_out_result

This variant of the transform function also has a return value. However, in this case, given the algorithm has two inputs and one output, its return type is a std::in_in_outÂ result.

Algorithms that return this type typically alias it. In this case, itâ€™s aliased to a std::ranges::binary_transform_result. However, the underlying value is consistent across all algorithms that useÂ it.

It is a struct with threeÂ fields:

• in1 - An iterator pointing to the end of the first input range - equivalent to Input1.end() in our previous example
• in2 - An iterator pointing to the end of the second input range - equivalent to Input2.end() in our previous example
• out - An iterator that is past the last element inserted into the output range

As before, these values tend to be accessed by structuredÂ binding:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Input1{1, 2, 3, 4, 5};
std::vector Input2{2, 3, 4, 5, 6};
std::vector<int> Output;
Output.resize(10);

auto [in1, in2, out]{
std::ranges::transform(Input1, Input2,
Output.begin(),
std::plus{})};

std::cout << "Output Size: " << Output.size();
Output.erase(out, Output.end());
std::cout << "\nNew Size: " << Output.size();
}
Output Size: 10
New Size: 5

## 3. The equal() Algorithm

The std::ranges::equal() algorithm returns true if the values in two ranges are equal, and in the sameÂ order:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Input1{1, 2, 3};
std::vector Input2{1, 2, 3};

if (std::ranges::equal(Input1, Input2)) {
std::cout << "Ranges are equal";
}
}
Ranges are equal

The equal() algorithm, and most other algorithms that perform comparisons on our objects, allow us to customize the nature of thoseÂ comparisons.

By default, the equal() algorithm compares our object using the == operator. If that operator returns true, the algorithm considers our objects to beÂ equal.

However, we can customize the comparison by passing an additional argument to our algorithm. That function will receive two of our objects, and should return true if the algorithm should consider the objectsÂ equal.

Below, we use std::ranges::equal() with a custom comparer to determine if the absolute values of our objects areÂ equal:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Input1{1, 2, 3};
std::vector Input2{-1, -2, -3};

auto AbsEqual{
[](int x, int y){
return std::abs(x) == std::abs(y);
}};

if (std::ranges::equal(Input1, Input2,
AbsEqual)) {
std::cout << "Ranges are equal";
}
}
Ranges are equal

In this case, we can implement the same behavior using a projectionÂ function.

In the following example, the third argument to equal() is {}, as we want to use the default comparison function. We then pass two projection functions, as we have two inputs. The first projection function will apply to objects in the first input, whilst the second function will apply to objects in the secondÂ input.

In this case, we use the same projection function for each inputÂ range:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Input1{1, 2, 3};
std::vector Input2{-1, -2, -3};

auto Abs{[](int x){ return std::abs(x); }};

if (std::ranges::equal(Input1, Input2, {},
Abs, Abs)) {
std::cout << "Ranges are equal";
}
}
Ranges are equal

Our requirements are not always met simply by projection functions. Without a custom comparison function, our projected values are still going to be compared by the == operator, which is not always what weÂ want.

Below, rather than using the == operator, our comparison function returns true if the objects are approximatelyÂ equal:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Input1{1.01f, 1.99f, 3.02f};
std::vector Input2{1.00f, 2.01f, 2.98f};

auto isApproximatelyEqual{
[](float x, float y){
return std::abs(x - y) < 0.1;
}};

if (std::ranges::equal(Input1, Input2,
isApproximatelyEqual)) {
std::cout << "Ranges are approximately equal";
}
}
Ranges are approximately equal

### Permutations

The equals() algorithm returns true if our ranges contain the same values, in the same order. But what if we didnâ€™t care about theÂ order?

If two collections contain the same elements in any order, theyâ€™re said to be permutations. The standard library includes an algorithm to test for this - std::ranges::permutation(), which is used in the same way as std::ranges::equal():

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Input1{1, 2, 3};
std::vector Input2{3, 1, 2};

if (!std::ranges::equal(Input1, Input2)) {
std::cout << "It's not equal";
}

if (std::ranges::is_permutation(
Input1, Input2)) {
std::cout << " but it's a permutation";
}
}
It's not equal but it's a permutation

Permutations do not need to have a different order. Two equal objects are also permutations, so if equal() would return true, so too would is_permutation(). We cover collection comparison algorithms in more detail in the nextÂ chapter.

## 4. The fill() Algorithm

The std::ranges::fill() algorithm is one of the simplest weâ€™ll come across. It accepts a range, as well as anÂ object.

It will then fill the range with copies of that object. Below, we create a std::vector that can hold 5 integers, and then we fill those slots with copies of the number 10:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector<int> Numbers;
Numbers.resize(5);
std::ranges::fill(Numbers, 10);
std::ranges::for_each(Numbers, Log);
}
10, 10, 10, 10, 10,

## 5. The generate() Algorithm

The std::ranges::generate() function populates a range by calling a function for each position in the range. The functionâ€™s return value is then placed into that position. The following example populates a range with incrementing integers, starting from 1:

#include <algorithm>
#include <iostream>
#include <array>

struct NumberGenerator {
int n{1};
int operator()(){ return n++; }
};

int main(){
NumberGenerator Generator;
std::array<int, 10> Numbers;

std::ranges::generate(Numbers, Generator);

for (int i : Numbers) {
std::cout << i << ", ";
}
}
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,

This example created a type that overrides the () operator, allowing us to create objects we can "call", sometimes called function objects or functors. We covered this concept in more detailÂ here:

In this example, we populate our range with a series of random numbers from 1 to 10:

#include <algorithm>
#include <iostream>
#include <array>
#include <random>

struct RandomNumberGenerator {
int operator()(){ return Dist(Engine); }

private:
static inline std::uniform_int_distribution
Dist{1, 10};
static inline std::random_device Device;
static inline std::mt19937 Engine{Device()};
};

int main(){
RandomNumberGenerator RNG;
std::array<int, 10> Numbers;

std::ranges::generate(Numbers, RNG);

for (int i : Numbers) {
std::cout << i << ", ";
}
}
3, 6, 3, 9, 4, 2, 10, 6, 10, 2,

This example uses the standard libraryâ€™s random number utilities, which we introducedÂ here:

The use of static and inline in the RandomNumberGenerator struct is something we cover later in thisÂ course.

## 6. The iota() Algorithm

The 9th letter of the Greek alphabet, $\iota$, is pronounced "iota" and, in programming contexts, is sometimes used to refer to a sequence of incrementing integers, eg $1, 2, 3, 4,Â 5$

The C++ standard library has a few variants of this idea. Our earlier example where we used std::ranges::generate() to construct a sequence can instead be replaced by std::iota(). This takes threeÂ arguments:

• An iterator pointing to where we want the range to start
• An iterator pointing to where we want the range to end
• An integer representing what we want the first number to be

This example populates an array with the numbersÂ 1-10

#include <array>
#include <iostream>
#include <numeric>

int main(){
std::array<int, 10> Numbers;

std::iota(Numbers.begin(), Numbers.end(), 1);

for (int i : Numbers) {
std::cout << i << ", ";
}
}
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,

Remember, iota is also available in a view form using std::views::iota, so defining a container for this purpose is oftenÂ unnecessary:

#include <iostream>
#include <ranges>
#include <vector>

int main(){
using std::views::iota, std::views::zip;
std::vector Strings{"Mon", "Tue", "Wed"};

for (const auto& Tuple :
zip(iota(1), Strings)) {
std::cout << std::get<0>(Tuple) << ": ";
std::cout << std::get<1>(Tuple) << '\\n';
}
}
1: Mon
2: Tue
3: Wed

## 7. The merge() Algorithm

The std::ranges::merge() algorithm accepts two sorted inputs and an output location. The output location will be populated with the sorted combination of our twoÂ inputs:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers1{1, 3, 5};
std::vector Numbers2{2, 4};
std::vector<int> Output;
Output.resize(
Numbers1.size() + Numbers2.size());

std::ranges::merge(Numbers1, Numbers2,
Output.begin());

std::ranges::for_each(Output, Log);
}
1, 2, 3, 4, 5,

If our range is not sorted, the behavior of merge() is undefined, likely resulting in a bug. We can sort collections using the sort() algorithms, introduced in our introductoryÂ lesson:

### Custom Comparer

The merge() algorithm and most other standard library algorithms that work on sorted data expect our data to be sorted in ascending order, based on the < operator. In other words, if A < B then A is expected to come before B in ourÂ collection.

We can change this by passing a custom comparison function. This function will receive two arguments from our ranges and should return true if the first argument comes before the secondÂ argument.

Below, we use this to state our objects are sorted in descendingÂ order:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers1{5, 3, 1};
std::vector Numbers2{4, 2};
std::vector<int> Output;
Output.resize(
Numbers1.size() + Numbers2.size());

auto Descending{
[](int a, int b){ return a > b; }};

std::ranges::merge(Numbers1, Numbers2,
Output.begin(),
Descending);

std::ranges::for_each(Output, Log);
}
5, 4, 3, 2, 1,

In this case, our simple lambda could be replaced with the std::greater template class, which simply runs the >Â operator:

std::ranges::merge(Numbers1, Numbers2,
Output.begin(),
std::greater{});

In the following example, we use a custom comparer to merge collections of a type that doesnâ€™t even support comparison operators. Instead, we have sorted them based on a classÂ variable:

#include <algorithm>
#include <iostream>
#include <vector>

class Character {/*...*/};

void Log(const Character& C) {/*...*/};

int main(){
using namespace std::string_literals;
std::vector<Character> A{
"Anna"s, "Bob"s, "Roderick"s};
std::vector<Character> B{"Arnold"s, "Bruce"s};
std::vector<Character> Output;
Output.resize(A.size() + B.size());

auto AlphabeticalByName{
[](const Character& A, const Character& B){
return A.Name < B.Name;
}};

std::ranges::merge(A, B, Output.begin(),
AlphabeticalByName);

std::ranges::for_each(Output, Log);
}
Anna, Arnold, Bob, Bruce, Roderick,

### std::ranges::is_sorted()

We can check if a range is sorted using the std::ranges::is_sorted()Â algorithm:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Numbers{1, 2, 3};

if (std::ranges::is_sorted(Numbers)) {
std::cout << "That range is sorted";
}
}
That range is sorted

This also accepts a comparison function, which lets us specify how we want the sort order to beÂ tested:

#include <algorithm>
#include <iostream>
#include <vector>

int main(){
std::vector Numbers{3, 2, 1};

if (std::ranges::is_sorted(
Numbers, std::greater{})) {
std::cout <<
"That range is sorted in descending order";
}
}
That range is sorted in descending order

### std::ranges::set_union()

The output of the merge() algorithm includes duplicates - for example, if both inputs include the number 3, the output will include bothÂ copies.

The std::ranges::set_union() algorithm only includes a single copy, if that is the behaviour weÂ need:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int Number) {/*...*/};

int main(){
std::vector A{1, 3, 5};
std::vector B{1, 2, 3, 4};

// Merge
std::vector<int> Merged;
Merged.resize(A.size() + B.size());
std::ranges::merge(A, B, Merged.begin());
std::cout << "Merge Result: ";
std::ranges::for_each(Merged, Log);

// Union
std::vector<int> Union;
Union.resize(A.size() + B.size());
auto [in1, int2, out] =
std::ranges::set_union(A, B, Union.begin());
Union.erase(out, Union.end());
std::cout << "\nUnion Result: ";
std::ranges::for_each(Union, Log);
}
Merge Result: 1, 1, 2, 3, 3, 4, 5,
Union Result: 1, 2, 3, 4, 5,

We cover std::ranges::set_union() and other set algorithms in detail in the nextÂ chapter.

## 8. The sample() Algorithm

The std::ranges::sample() algorithm copies a random sample of objects from one range intoÂ another.

#include <algorithm>
#include <iostream>
#include <vector>
#include <random>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};
std::vector<int> Output;
Output.resize(3);

auto RNG{
std::mt19937{std::random_device{}()}};

std::ranges::sample(Numbers, Output.begin(),
Output.size(), RNG);

std::ranges::for_each(Output, Log);
}
2, 3, 5,

Sampling is done "without replacement" - that is, once an object has been sampled from our input set, it cannot be chosen again. The effect of this is that each object in our input can only appear in our sampled output once. It also means the maximum size of our sample set is the same as the maximum size of ourÂ input.

### std::ranges::shuffle()

Objects in our sample will appear in the same relative order they were in theÂ input.

If we want to randomize the order, we can shuffle the output. This is available as a separateÂ algorithm.

std::ranges::shuffle() accepts two arguments - the range we want to shuffle, and a random number generator to use. Below, we take a sample of our objects, and then shuffle thatÂ sample:

#include <algorithm>
#include <iostream>
#include <vector>
#include <random>

void Log(int Number) {/*...*/};

int main(){
std::vector Numbers{1, 2, 3, 4, 5};
std::vector<int> Output;
Output.resize(3);

auto RNG{
std::mt19937{std::random_device{}()}};

std::ranges::sample(Numbers, Output.begin(),
Output.size(), RNG);
std::ranges::shuffle(Output, RNG);

std::ranges::for_each(Output, Log);
}
1, 4, 3,

## Summary

This lesson has introduced eight key algorithms from the C++ Standard Library, demonstrating their utility in a variety of programming scenarios. Through examples, we've seen how these algorithms can simplify code, improve readability, and enhance performance. WeÂ covered:

• std::ranges::for_each() for iterating over collections.
• How std::ranges::transform() can be used for modifying collections or combining two ranges based on a function.
• The use of std::ranges::equal() for comparing two ranges, with the ability to customize comparison logic.
• Utilizing std::ranges::fill() to populate a range with a single value.
• Generating sequences or random values in a range using std::ranges::generate().
• Creating numeric sequences with std::iota() and its utility in programming.
• Merging two sorted ranges into a single sorted range using std::ranges::merge() and customizing the sorting criteria.
• Sampling a subset of elements from a range randomly with std::ranges::sample() and shuffling elements with std::ranges::shuffle().

Next Lesson

### Parallel Algorithm Execution

Multithreading in C++ standard library algorithms using execution policies
New: AI-Powered AssistanceAI Assistance

### Questions and HelpNeed Help?

Get instant help using our free AI assistant, powered by state-of-the-art language models.

Updated
Lesson Contents

### 8 Key Standard Library Algorithms

An introduction to 8 more useful algorithms from the standard library, and how we can use them alongside views, projections, and other techniques

This lesson is part of theÂ course:

### Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access

###### 8 Key Standard Library Algorithms

An introduction to 8 more useful algorithms from the standard library, and how we can use them alongside views, projections, and other techniques

This lesson is part of the course:

## Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

### This course includes:

• 124 Lessons
• 550+ Code Samples
• 96% Positive Reviews
• Regularly Updated
• Help and FAQ
Next Lesson

### Parallel Algorithm Execution

Multithreading in C++ standard library algorithms using execution policies