Standard Template Library

STL Algorithms

Master C++ STL algorithms — sort, find, transform, accumulate, count_if, remove_if, partition, and composing algorithms with lambdas and ranges.

STL Algorithms

The <algorithm> and <numeric> headers provide over 100 generic algorithms that work on any range defined by iterators. These algorithms are composable, well-tested, and often more efficient than hand-written loops. Writing sort(v.begin(), v.end()) is clearer, shorter, and less error-prone than implementing quicksort yourself.


The Algorithm Pattern

All STL algorithms operate on iterator ranges — a begin and end iterator that define the sequence:

cpp
#include <algorithm>
#include <vector>
using namespace std;

vector<int> v = {5, 2, 8, 1, 9, 3};

// [begin, end) — includes begin, excludes end
sort(v.begin(), v.end());           // Entire vector
sort(v.begin(), v.begin() + 3);    // First 3 elements only
sort(v.begin(), v.end(), greater<int>());  // Descending

Searching Algorithms

cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

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

    // find — returns iterator to first match or end
    auto it = find(v.begin(), v.end(), 9);
    cout << (it != v.end() ? "Found 9" : "Not found") << "\n";  // Found 9

    // find_if — find first element matching predicate
    auto it2 = find_if(v.begin(), v.end(), [](int x) { return x > 7; });
    cout << "First > 7: " << *it2 << "\n";   // 9

    // count / count_if
    cout << count(v.begin(), v.end(), 1) << "\n";   // 2 (two 1s)
    cout << count_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }) << "\n";  // 3

    // any_of, all_of, none_of
    cout << any_of(v.begin(), v.end(),  [](int x) { return x > 8; }) << "\n";  // 1
    cout << all_of(v.begin(), v.end(),  [](int x) { return x > 0; }) << "\n";  // 1
    cout << none_of(v.begin(), v.end(), [](int x) { return x < 0; }) << "\n";  // 1

    // Binary search (sorted range required)
    vector<int> sorted = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    cout << binary_search(sorted.begin(), sorted.end(), 7) << "\n";  // 1

    // lower_bound / upper_bound
    auto lb = lower_bound(sorted.begin(), sorted.end(), 5);   // First >= 5
    auto ub = upper_bound(sorted.begin(), sorted.end(), 5);   // First > 5
    cout << *lb << "\n";  // 5
    cout << *ub << "\n";  // 6

    return 0;
}

Sorting

cpp
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;

struct Person {
    string name;
    int    age;
};

int main() {
    vector<int> v = {5, 2, 8, 1, 9, 3, 7, 4, 6};

    // Ascending (default)
    sort(v.begin(), v.end());

    // Descending
    sort(v.begin(), v.end(), greater<int>());

    // Partial sort — only sort the 3 smallest to front
    vector<int> v2 = {5, 2, 8, 1, 9, 3};
    partial_sort(v2.begin(), v2.begin() + 3, v2.end());
    // v2[0..2] = {1, 2, 3} (sorted), rest is unspecified

    // Stable sort — preserves relative order of equal elements
    vector<Person> people = {{"Alice", 25}, {"Bob", 25}, {"Charlie", 30}};
    stable_sort(people.begin(), people.end(),
                [](const Person& a, const Person& b) {
                    return a.age < b.age;
                });
    for (const auto& p : people) cout << p.name << " ";
    cout << "\n";  // Alice Bob Charlie (Alice before Bob — stable)

    // nth_element — O(n) partial sort; ensures correct element at position n
    vector<int> v3 = {5, 2, 8, 1, 9, 3, 7};
    nth_element(v3.begin(), v3.begin() + 3, v3.end());
    cout << "4th smallest: " << v3[3] << "\n";  // 5

    // is_sorted
    cout << is_sorted(v.begin(), v.end()) << "\n";  // 1 (true — descending sorted)

    return 0;
}

Transforming Elements

cpp
#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <cmath>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // transform — apply function to each element
    vector<int> squared(v.size());
    transform(v.begin(), v.end(), squared.begin(),
              [](int x) { return x * x; });
    // squared = {1, 4, 9, 16, 25}

    // transform two ranges
    vector<int> a = {1, 2, 3}, b = {10, 20, 30};
    vector<int> sums(3);
    transform(a.begin(), a.end(), b.begin(), sums.begin(),
              [](int x, int y) { return x + y; });
    for (int n : sums) cout << n << " ";
    cout << "\n";  // 11 22 33

    // for_each — apply void function to each element
    vector<double> vals = {1.0, 4.0, 9.0, 16.0, 25.0};
    for_each(vals.begin(), vals.end(), [](double& x) { x = sqrt(x); });
    for (double d : vals) cout << d << " ";
    cout << "\n";  // 1 2 3 4 5

    // replace / replace_if
    vector<int> w = {1, 2, 0, 4, 0, 6};
    replace(w.begin(), w.end(), 0, -1);    // Replace 0 with -1
    replace_if(w.begin(), w.end(), [](int x) { return x < 0; }, 0);  // Restore

    return 0;
}

Accumulation and Reduction

cpp
#include <iostream>
#include <numeric>
#include <vector>
#include <functional>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // accumulate — reduce to single value
    int sum     = accumulate(v.begin(), v.end(), 0);
    int product = accumulate(v.begin(), v.end(), 1, multiplies<int>());
    cout << sum     << "\n";   // 15
    cout << product << "\n";   // 120

    // iota — fill with increasing values
    vector<int> range(10);
    iota(range.begin(), range.end(), 1);   // {1,2,3,...,10}

    // inner_product — dot product
    vector<int> a = {1, 2, 3};
    vector<int> b = {4, 5, 6};
    int dot = inner_product(a.begin(), a.end(), b.begin(), 0);
    cout << dot << "\n";   // 4+10+18 = 32

    // partial_sum — running sum
    vector<int> prefix(v.size());
    partial_sum(v.begin(), v.end(), prefix.begin());
    for (int n : prefix) cout << n << " ";
    cout << "\n";   // 1 3 6 10 15

    return 0;
}

Removing Elements

cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 2, 4, 2, 5};

    // IMPORTANT: remove() doesn't resize — it moves elements to back
    // Must follow with erase() — the "erase-remove idiom"
    auto newEnd = remove(v.begin(), v.end(), 2);
    v.erase(newEnd, v.end());
    // v = {1, 3, 4, 5}
    for (int n : v) cout << n << " ";
    cout << "\n";

    // remove_if
    vector<int> w = {1, 2, 3, 4, 5, 6, 7, 8};
    w.erase(remove_if(w.begin(), w.end(), [](int x) { return x % 2 == 0; }),
            w.end());
    for (int n : w) cout << n << " ";
    cout << "\n";  // 1 3 5 7

    // unique — remove consecutive duplicates (sort first for all duplicates)
    vector<int> u = {1, 1, 2, 2, 3, 1, 1};
    sort(u.begin(), u.end());               // Sort first
    u.erase(unique(u.begin(), u.end()), u.end());
    for (int n : u) cout << n << " ";
    cout << "\n";  // 1 2 3

    return 0;
}

Partition and Set Operations

cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    // partition — rearrange so predicate-true elements come first
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
    auto pivot = partition(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
    // Even elements before odd — order within each group not guaranteed

    // stable_partition — preserves relative order within each group
    vector<int> w = {1, 2, 3, 4, 5, 6};
    stable_partition(w.begin(), w.end(), [](int x) { return x > 3; });
    for (int n : w) cout << n << " ";
    cout << "\n";  // 4 5 6 1 2 3

    // Set operations (on sorted ranges)
    vector<int> a = {1, 2, 3, 4, 5};
    vector<int> b = {3, 4, 5, 6, 7};
    vector<int> inter, uni, diff;

    set_intersection(a.begin(), a.end(), b.begin(), b.end(),
                     back_inserter(inter));
    set_union(a.begin(), a.end(), b.begin(), b.end(),
              back_inserter(uni));
    set_difference(a.begin(), a.end(), b.begin(), b.end(),
                   back_inserter(diff));

    for (int n : inter) cout << n << " ";  // 3 4 5
    cout << "\n";
    for (int n : diff)  cout << n << " ";  // 1 2 (in a but not b)
    cout << "\n";

    return 0;
}

Practical Example: Data Pipeline

cpp
#include <iostream>
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <iomanip>
using namespace std;

struct Sale {
    string product;
    int    quantity;
    double unitPrice;
    double total() const { return quantity * unitPrice; }
};

int main() {
    vector<Sale> sales = {
        {"Laptop",   5, 45000.0},
        {"Mouse",   20,   999.0},
        {"Keyboard", 8,  1499.0},
        {"Monitor",  3, 18000.0},
        {"Headset",  12, 2499.0},
        {"Webcam",   6,  2999.0},
    };

    // 1. Filter: Only high-value sales (total > 10,000)
    vector<Sale> highValue;
    copy_if(sales.begin(), sales.end(), back_inserter(highValue),
            [](const Sale& s) { return s.total() > 10000; });

    // 2. Sort by total descending
    sort(highValue.begin(), highValue.end(),
         [](const Sale& a, const Sale& b) { return a.total() > b.total(); });

    // 3. Print top sales
    cout << "Top Sales (>₹10,000):\n";
    cout << string(45, '-') << "\n";
    for (const auto& s : highValue) {
        cout << left << setw(12) << s.product
             << " qty=" << setw(3) << s.quantity
             << " total=₹" << fixed << setprecision(0) << s.total() << "\n";
    }

    // 4. Total revenue from filtered sales
    double revenue = accumulate(highValue.begin(), highValue.end(), 0.0,
                                [](double acc, const Sale& s) {
                                    return acc + s.total();
                                });
    cout << "\nRevenue from top sales: ₹" << revenue << "\n";

    // 5. Max single product revenue
    auto maxIt = max_element(sales.begin(), sales.end(),
                             [](const Sale& a, const Sale& b) {
                                 return a.total() < b.total();
                             });
    cout << "Best product: " << maxIt->product << " (₹" << maxIt->total() << ")\n";

    return 0;
}

Summary

  • STL algorithms work on iterator ranges — (begin, end) — and work with any container
  • sort, stable_sort, partial_sort, nth_element — sorting family
  • find, find_if, count_if, any_of, all_of — searching family
  • transform, for_each, replace_if — transformation family
  • accumulate, inner_product, partial_sum — reduction family (in <numeric>)
  • remove_if + erase (the erase-remove idiom) — removal family
  • Always use back_inserter(v) as output iterator when you want to push to a vector
  • Lambdas as comparators and predicates are the modern way to customise algorithm behaviour

Next, we'll explore iterators — the abstraction that connects containers to algorithms, and how to write your own.