Notifications

No notifications

/Phase 4

STL Algorithms — sort, find, transform & friends

Verbs for Your Containers ⚙️

The and headers give you ~100 free functions that work on any container with iterators. Master a dozen and you'll write less, faster, clearer code.

The Daily Dozen

#include <algorithm>
#include <numeric>
using namespace std;

vector<int> v{3, 1, 4, 1, 5, 9, 2, 6};

sort(v.begin(), v.end()); // 1, 1, 2, 3, 4, 5, 6, 9 sort(v.begin(), v.end(), greater<int>()); // descending

bool found = binary_search(v.begin(), v.end(), 5);

auto it = find(v.begin(), v.end(), 4);

int sum = accumulate(v.begin(), v.end(), 0); int evens = count_if(v.begin(), v.end(), [](int x){ return x % 2 == 0; });

bool any = any_of(v.begin(), v.end(), [](int x){ return x > 5; }); bool all = all_of(v.begin(), v.end(), [](int x){ return x > 0; });

transform(v.begin(), v.end(), v.begin(), [](int x){ return x * x; });

reverse(v.begin(), v.end());

auto [mn, mx] = minmax_element(v.begin(), v.end());

Range-Based (C++20)

#include <ranges>
namespace rv = std::views;

auto squared_evens = v | rv::filter([](int x){ return x % 2 == 0; }) | rv::transform([](int x){ return x * x; });

for (int x : squared_evens) cout << x << ' ';

On this page

Detailed Theory

Algorithms work on iterator pairs [begin, end) — half-open. They don't know or care which container you have, only that you can step through it.

The Big Categories

GroupExamples
Non-modifyingfind, count, any_of, all_of, equal, mismatch
Modifyingcopy, transform, replace, fill, generate
Removingremove, unique (use erase-remove idiom)
Sortingsort, stable_sort, partial_sort, nth_element
Binary searchbinary_search, lower_bound, upper_bound (need sorted)
Numeric ()accumulate, reduce, partial_sum, iota, gcd
Min/maxmin, max, minmax, min_element, max_element
Heapmake_heap, push_heap, pop_heap

sort

sort(v.begin(), v.end());                         // ascending
sort(v.begin(), v.end(), greater<int>());         // descending
sort(people.begin(), people.end(),
     [](const Person& a, const Person& b){ return a.age < b.age; });

std::sort is introsort — quicksort + heapsort fallback. Average and worst case O(n log n). Not stable. Use stable_sort (O(n log² n) worst) when equal keys must keep order.

Sort by Multiple Keys

sort(people.begin(), people.end(), [](const auto& a, const auto& b){
    if (a.dept != b.dept) return a.dept < b.dept;
    return a.salary > b.salary;
});

find, find_if

auto it = find(v.begin(), v.end(), 7);
if (it != v.end()) cout << "found at index " << (it - v.begin());

auto it2 = find_if(v.begin(), v.end(), [](int x){ return x > 100; });

> Always compare with .end() — that's the "not found" sentinel.

count, count_if

int n_zeros = count(v.begin(), v.end(), 0);
int n_big   = count_if(v.begin(), v.end(),
                       [](int x){ return x > 1000; });

any_of, all_of, none_of

bool has_negative = any_of (v.begin(), v.end(), [](int x){ return x < 0; });
bool all_positive = all_of (v.begin(), v.end(), [](int x){ return x > 0; });
bool none_zero    = none_of(v.begin(), v.end(), [](int x){ return x == 0; });

These short-circuit — they stop as soon as the answer is decided.

transform — Map

vector<int> v{1, 2, 3, 4};
vector<int> squares(v.size());
transform(v.begin(), v.end(), squares.begin(),
          [](int x){ return x * x; });
// squares: 1, 4, 9, 16

// Two-input variant: vector<int> a{1,2,3}, b{10,20,30}, sums(3); transform(a.begin(), a.end(), b.begin(), sums.begin(), plus<>{}); // sums: 11, 22, 33

accumulate, reduce — Fold

#include <numeric>
int total   = accumulate(v.begin(), v.end(), 0);
int product = accumulate(v.begin(), v.end(), 1, multiplies<int>());

string joined = accumulate(words.begin(), words.end(), string{}, [](const string& acc, const string& w){ return acc.empty() ? w : acc + ", " + w; });

std::reduce (C++17) is like accumulate but allows parallel/unordered execution.

copy, copy_if

vector<int> evens;
copy_if(v.begin(), v.end(),
        back_inserter(evens),
        [](int x){ return x % 2 == 0; });

back_inserter (from ) appends to evens via push_back.

remove + erase — The Erase-Remove Idiom

std::remove does not actually remove anything from a vector. It shuffles "kept" elements forward and returns the new logical end. You then erase from there to the real end.

v.erase(remove(v.begin(), v.end(), 3), v.end());      // remove all 3s

v.erase(remove_if(v.begin(), v.end(), [](int x){ return x < 0; }), v.end()); // drop negatives

C++20 simplifies this to std::erase / std::erase_if:

erase(v, 3);
erase_if(v, [](int x){ return x < 0; });

unique — Collapse Adjacent Duplicates

Requires the range to be sorted (or at least: duplicates already adjacent).

sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());     // dedupe

Binary Search Family (sorted ranges)

AlgorithmReturns
binary_searchbool — present or not
lower_bounditerator to first >= value
upper_bounditerator to first > value
equal_rangepair {lower, upper}

sort(v.begin(), v.end());
auto it = lower_bound(v.begin(), v.end(), 5);
if (it != v.end() && *it == 5)
    cout << "found 5 at index " << (it - v.begin());

Min / Max

int a = min({3, 1, 4, 1, 5, 9});
auto [it_lo, it_hi] = minmax_element(v.begin(), v.end());
cout << "min=" << *it_lo << " max=" << *it_hi;

Numeric Goodies —

vector<int> v(10);
iota(v.begin(), v.end(), 1);              // 1, 2, 3, ..., 10
int s = accumulate(v.begin(), v.end(), 0); // 55

vector<int> psum(v.size()); partial_sum(v.begin(), v.end(), psum.begin()); // 1, 3, 6, 10, ...

cout << gcd(12, 18); // 6 cout << lcm(4, 6); // 12

C++20 Ranges — Pipelines

The classic algorithms work great but the iterator-pair style is verbose. Ranges fix that:

#include <ranges>
namespace rv = std::views;
namespace ra = std::ranges;

vector<int> v{1, 2, 3, 4, 5};

// Pipeline: filter | transform auto result = v | rv::filter([](int x){ return x % 2 == 0; }) | rv::transform([](int x){ return x * x; });

// Lazy! Materialise: vector<int> out(result.begin(), result.end()); // {4, 16}

ra::sort(v); // no .begin()/.end() needed ra::find(v, 3);

Treat ranges as "the modern way" — but the classic algorithms aren't going anywhere.

Lambdas as Predicates

Most STL algorithms take a callable. Lambdas keep the logic local:

sort(people.begin(), people.end(),
     [](const Person& a, const Person& b){ return a.age < b.age; });

count_if(v.begin(), v.end(), [](int x){ return x > threshold; });

Capture by value [=], by reference [&], or specifically [threshold].

Pitfalls

BugFix
remove(v.begin(), v.end(), x); doesn't shrink the vectorPair with v.erase(...) or use C++20 std::erase
binary_search on unsorted rangeSort first
Off-by-one with end() (it points past last)Always compare it != v.end()
Iterator invalidation during loopRe-fetch iterators after push_back etc.

Cheat-Sheet

VerbAlgorithm
Sortsort, stable_sort, partial_sort
Findfind, find_if, binary_search, lower_bound
Countcount, count_if
Test all/anyall_of, any_of, none_of
Maptransform
Foldaccumulate, reduce
Filter & copycopy_if, remove_copy_if
In-place deleteerase-remove idiom, C++20 erase_if
Min/maxmin_element, max_element, minmax
Range pipelinev \views::filter \views::transform

You can now express most data manipulation as a one-liner. Iterators next — the glue that makes all of this work.