Lesson 18 • Advanced
STL Algorithm Mastery
By the end of this lesson you'll replace hand-written loops with the C++ Standard Library's <algorithm> and <numeric> tools — sorting with your own rules, searching, summing, transforming, and safely deleting elements — using one line where you used to write fifteen.
What You'll Learn
- Sort a range with std::sort and a custom comparator lambda
- Search with std::find, count with std::count and std::count_if
- Fold a range to one value with std::accumulate (from <numeric>)
- Reshape data with std::transform and act with std::for_each
- Find extremes with std::max_element / std::min_element
- Delete elements correctly with the erase-remove idiom
[](int n){ return n > 0; }). Every algorithm here takes a range — a pair of iterators, v.begin() and v.end() — so make sure that idea feels familiar.💡 Real-World Analogy
Think of STL algorithms as power tools. You could cut every plank with a hand saw (your own for loop), but a builder reaches for the right power tool: a circular saw to cut (sort), a stud finder to locate (find), a tape measure to total up (accumulate). Each tool clamps onto a stretch of work — that's the iterator range, from begin() to end(). You supply the rule (a lambda: "cut here", "keep this one") and the tool does the repetitive work, faster and with fewer mistakes than doing it by hand.
1. Sort, Find & Count
std::sort rearranges a range in place — ascending by default. To sort by your rule, pass a third argument: a comparator. A comparator is a lambda that takes two elements and returns a bool answering one question — "should a come before b?" Return a > b and you've flipped it to descending. find hunts for an exact value and hands back an iterator (compare it to end() to check for a miss), while count and count_if tally matches. Read this worked example, run it, then you'll write your own.
Worked example: sort, comparator, find, count
Read every comment, run it, and check the output matches.
#include <iostream>
#include <vector>
#include <algorithm> // sort, find, count, count_if, min/max_element
using namespace std;
int main() {
vector<int> nums = {42, 17, 8, 95, 23, 61, 3};
// sort — rearranges the range in place, ascending by default.
// You pass a RANGE: begin() ... end() (end is one-past-the-last).
sort(nums.begin(), nums.end());
cout << "Ascending: ";
for (int n : nums) cout << n << " "; // 3 8 17 23 42 61 95
cout << endl;
// Custom comp
...Your turn. The program below is almost complete — fill in the two blanks marked ___ using the hints, then run it and check it against the expected output.
🎯 Your turn: descending sort & count_if
Fill in the comparator and the predicate, then verify your output.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
vector<int> scores = {72, 48, 91, 63, 30, 88, 55};
// 1) Sort scores HIGHEST first using a comparator lambda.
// A comparator returns "should a come before b?".
sort(scores.begin(), scores.end(), [](int a, int b) {
return ___; // 👉 a > b (so bigger values come first)
});
cout << "Ranked:
...2. Accumulate, Transform & for_each
std::accumulate (from <numeric>, not <algorithm>) folds a whole range into a single value — its third argument is both the starting total and the type of the result. std::transform applies a function to every element and writes the results into another range. std::for_each runs an action on each element without producing a new range. And min_element / max_element return an iterator to the smallest or largest value — remember to dereference it with * to read the value out.
Worked example: accumulate, transform, min/max, for_each
See how a range folds to one value, and how transform reshapes data.
#include <iostream>
#include <vector>
#include <algorithm> // transform, for_each, min_element, max_element
#include <numeric> // accumulate (lives here, NOT in <algorithm>)
using namespace std;
int main() {
vector<int> prices = {1299, 2499, 599, 3999, 899}; // cents
// accumulate — fold a whole range into ONE value.
// The 3rd argument (0) is the starting total AND fixes the type.
int totalCents = accumulate(prices.begin(), prices.end(), 0);
cout << "Total cents:
...Now you try. Total a list of workout minutes, then convert each one to hours. Fill in the two blanks — watch the type on the accumulate seed and the division:
🎯 Your turn: total & convert with accumulate + transform
Seed accumulate correctly and divide by 60.0 to keep decimals.
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
// 🎯 YOUR TURN — fill in the blanks, then run.
vector<int> minutes = {30, 45, 15, 60, 20}; // workout lengths
// 1) Add them all up with accumulate. Start the total at 0.
int totalMins = accumulate(minutes.begin(), minutes.end(), ___); // 👉 0
cout << "Total minutes: " << totalMins << endl;
// 2) Convert each value to HOURS (a double) using transform.
...3. Deleting Elements: the Erase-Remove Idiom
This is the one that surprises everybody. std::remove and std::remove_if do not actually delete anything. An algorithm only sees a pair of iterators — it has no power to resize the container. So remove just shuffles the elements you're keeping to the front and returns an iterator to the new logical end; the leftover slots are still there. To truly shrink the container you pass that iterator to the container's own erase() method. Doing both together is the famous erase-remove idiom.
Worked example: remove vs erase, and remove_if
Watch the size NOT change after remove, then change after erase.
#include <iostream>
#include <vector>
#include <algorithm> // remove, remove_if
using namespace std;
void printVec(const string& label, const vector<int>& v) {
cout << label << ": ";
for (int n : v) cout << n << " ";
cout << endl;
}
int main() {
// remove() does NOT delete anything. It shifts the elements you keep
// to the front and returns an iterator to the new "logical end".
// Everything after that point is leftover junk — still in the vector!
vector<int> dat
...Pro Tips
- 💡 A comparator must be a strict weak ordering: use
<not<=. Returningtruefor equal elements breakssortand can crash your program. - 💡 min/max_element return iterators, not values. Dereference with
*:int hi = *max_element(v.begin(), v.end());. - 💡 Seed accumulate with the right type:
accumulate(v.begin(), v.end(), 0.0)sums asdouble;0would truncate toint. - 💡 Always pair remove_if with erase — by itself it leaves stale elements behind the new end.
Common Errors (and the fix)
- "My remove didn't remove anything!":
removeonly shifts elements and returns the new end — it can't resize the container. You must follow it withv.erase(newEnd, v.end()). That's the erase-remove idiom. - Crash / "invalid comparator": your comparator returned
truefor equal elements (you wrote<=instead of<). A comparator must be a strict weak ordering — strictly<, never<=. - "'accumulate' was not declared in this scope": you forgot
#include <numeric>.accumulatelives there, not in<algorithm>. - Garbage value from max_element: you forgot the
*. The function returns an iterator; read the value with*max_element(...). - Off-by-one / mismatched ranges:
end()points one past the last element, so a range is[begin, end). Passingv.end()as a start, or mixing iterators from two different containers, is undefined behaviour.
📋 Quick Reference
| Task | Code | Returns |
|---|---|---|
| Sort ascending | sort(v.begin(), v.end()) | void (in place) |
| Sort by rule | sort(b, e, [](int a, int b){ return a>b; }) | void (in place) |
| Find a value | find(v.begin(), v.end(), 42) | iterator / end() |
| Count matches | count_if(b, e, pred) | how many |
| Sum a range | accumulate(b, e, 0) | the total |
| Map each element | transform(b, e, out, fn) | writes to out |
| Largest value | *max_element(b, e) | the value |
| Delete matches | v.erase(remove_if(b, e, pred), e) | shrinks v |
Frequently Asked Questions
Q: Do I always need to include <algorithm> and <numeric>?
Yes. sort, find, count_if, transform, for_each, remove_if, min_element and max_element all live in <algorithm>. accumulate lives in <numeric>. If you forget the include you'll get an 'X was not declared in this scope' error.
Q: Why does erasing after remove_if look so awkward?
remove_if only shuffles the kept elements to the front and returns an iterator to the new logical end — it cannot resize the container because algorithms only see iterators, not the container itself. You pass that returned iterator to the container's own erase() to actually shrink it. This two-step combo is the 'erase-remove idiom'.
Q: What does a comparator lambda return?
A bool. It answers one question: 'should a come before b?' Return true when a should sort earlier. For ascending order return a < b; for descending return a > b. It must NOT return true for equal values, or you break sort's rules.
Q: What's the difference between find and find_if?
find looks for a specific value (find(v.begin(), v.end(), 42)). find_if looks for the first element that makes a predicate return true (find_if(v.begin(), v.end(), [](int n){ return n > 50; })). Both return end() when nothing matches.
Q: Why does accumulate start with a value like 0?
That third argument is the starting total (the 'seed') and it also fixes the result type. accumulate(v.begin(), v.end(), 0) sums into an int; accumulate(v.begin(), v.end(), 0.0) sums into a double. Seeding with the wrong type silently truncates your decimals.
Mini-Challenge: Temperature Report
No blanks this time — just a brief and an outline to keep you on track. Combine sort, min_element/max_element, accumulate, and count_if into one small report. Build it, run it, and check your output against the example in the comments.
🎯 Mini-Challenge: build a temperature report
Sort, find the extremes, average with accumulate, and count warm days yourself.
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
// 🎯 MINI-CHALLENGE: Temperature report
vector<int> temps = {18, 25, 31, 14, 22, 29, 16};
// 1. Sort temps from COLDEST to hottest (a comparator lambda, or plain sort).
// 2. Use min_element / max_element to print the low and the high.
// Remember they return iterators — dereference with * to read the value.
// 3. Use accumulate to get the total, then divi
...🎉 Lesson Complete
- ✅
sortorders a range in place; a comparator lambda sets your own rule (strict<, never<=) - ✅
findreturns an iterator;count/count_iftally matches - ✅
accumulate(from<numeric>) folds a range to one value — seed it with the right type - ✅
transformreshapes data into another range;for_eachacts on each element - ✅
min_element/max_elementreturn iterators — dereference with* - ✅
remove_ifdoesn't shrink anything — pair it witherase()(the erase-remove idiom) - ✅ Next lesson: Advanced Containers — the internals of vector, list, map, unordered_map, and deque
Sign up for free to track which lessons you've completed and get learning reminders.