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

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.

`for_each()`

AlgorithmThe `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*,

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

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

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

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.

`transform()`

AlgorithmThe `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:

- The input range
- 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.
- 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,
```

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

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

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
```

`equal()`

AlgorithmThe `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
```

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.

`fill()`

AlgorithmThe `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,
```

`generate()`

AlgorithmThe `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

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.

`iota()`

AlgorithmThe 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
```

`merge()`

AlgorithmThe `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:

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.

`sample()`

AlgorithmThe `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,
```

â€” First Published

Posted

This lesson is part of theÂ course:### Professional C++

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