# 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.  ###### Ryan McCombe
Posted

The next chapter of the course introduces over 50 algorithms from the standard library. We can use these to solve a huge range of problems we’re likely to encounter when we’re working on 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. For every element in the range, the function is then invoked, 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,


### Iterators

All of the algorithms we cover in this lesson, and the next chapter, have a variation that works with iterator pairs instead of ranges. Below, we use the iterator variant of for_each() to pass only a subset of our collection to the Log() function:

#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,


Bearing in mind views are also ranges, they can be used directly with a range-based for loop:

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

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

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

for (auto i : std::ranges::subrange(
Numbers.begin() + 1,
Numbers.end() - 1)
) {
std::cout << i << ", ";
}
}

2, 3, 4,


We won’t always call these techniques out when covering future algorithms, but they always tend to apply. We can use views, iterators, subranges, 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 allocator 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 - A past-the-end iterator for the input range (equivalent to Input.end())
• out - An iterator that is past the last element inserted into the output range

These are usually accessed by structured binding, and the second property tends to be the most useful. In the transform() case, we always know how many objects will be copied - it will be equivalent to the size of the input range.

But in more complex algorithms, that is not always the case - the output is quite often smaller than the input, and we can’t know the size of the output until the algorithm completes.

In these cases, we can use the algorithm’s return value to better understand the effect the algorithm had.

We show some examples 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 - A past-the-end iterator for the first input range - equivalent to Input1.end() in our previous example
• in2 - A past-the-end iterator for 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, and out is the most useful for follow-up operations:

#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 of the algorithm should consider the objects equal.

Below, we use std::ranges::equal with a custom comparer to determine if the absolute value 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 equal";
}
}

Ranges are 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 collection contains 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 or iterator pair, 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 by 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, we introduced the sort algorithm 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};

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);

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,


### Edit History

• First Published ###### Ryan McCombe
Posted
This lesson is part of the course:

### Professional C++

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

###### 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:

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

### C++ Search Algorithms

An introduction to the 8 main searching algorithms in the C++ standard library, including find, find_if, find_if_not, find_first_of, adjacent_find, search_n, search, and find_end. 