Introduction
Loops that search, count and sort are written again and again, and each is a chance to make a small mistake. The standard library provides well-tested versions that say exactly what they are doing. A line that reads sort(...) needs no explanation.
How it works
An iterator is like a pointer into a container. *it reads the element, and ++it moves on. v.begin() points at the first element, and v.end() points just past the last, which is why it must never be read. Together they describe a range.
Algorithms come from <algorithm> and take such a range:
| Algorithm | Does |
|---|---|
sort(b, e) | sorts, smallest first |
sort(b, e, cmp) | sorts by your compare function |
reverse(b, e) | reverses the order |
find(b, e, x) | returns an iterator to x, or e |
count(b, e, x) | counts the matches |
find_if, count_if | the same, using a test function |
min_element, max_element | returns an iterator to the smallest or largest |
transform(b, e, out, f) | applies f to each element |
unique(b, e) | removes neighbouring repeats |
lower_bound, upper_bound, binary_search | fast search of sorted data |
Passing a function. Without brackets, a function's name is a value, so it can be handed to an algorithm. A function that returns true or false is called a predicate:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
bool is_negative(int x) {
return x < 0;
}
int main() {
vector<int> v = {4, -2, 7, -9};
auto it = find(v.begin(), v.end(), 7);
if (it != v.end()) {
cout << "found at " << it - v.begin() << endl;
}
cout << count_if(v.begin(), v.end(), is_negative) << endl;
cout << *find_if(v.begin(), v.end(), is_negative) << endl;
return 0;
}Removing repeats. unique needs sorted data, and it only moves the unique items to the front. The usual form erases the leftovers: v.erase(unique(v.begin(), v.end()), v.end());.
Syntax
sort(v.begin(), v.end());
auto it = find(v.begin(), v.end(), x);
if (it != v.end()) { ... }
int n = count_if(v.begin(), v.end(), is_even);
Example
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
bool larger_first(int a, int b) {
return a > b;
}
int main() {
vector<int> v = {5, 1, 4};
sort(v.begin(), v.end());
cout << v[0] << " " << v[2] << endl;
sort(v.begin(), v.end(), larger_first);
cout << v[0] << " " << v[2] << endl;
cout << *max_element(v.begin(), v.end()) << endl;
return 0;
}Line by line
sortwith two iterators puts the numbers in increasing order.v[0]andv[2]are now the smallest and the largest.- The second
sortis given a compare function, which makes it sort from largest to smallest. max_elementreturns an iterator, and*reads the value.
Expected result
1 5
5 1
5
In the real world
The standard algorithms are heavily optimised and thoroughly tested, so using them means faster and safer code. Experienced C++ programmers tend to reach for an algorithm first, and write a loop only if nothing fits.
Common mistakes
- Reading
*v.end(), which is past the last element. - Not checking
findagainstend(). - Using
binary_searchon unsorted data. - Forgetting that
uniqueneeds sorted data, and theeraseafterwards.
Best practices
- Check for a standard algorithm before you write a loop.
- Compare a compare function strictly: use
<or>, never<=. - Include
<algorithm>, and do not rely on another header to bring it in.
Summary
Iterators point into containers, and begin and end describe a range. Algorithms take a range and do common jobs reliably. Functions can be passed to them to customise their behaviour.
Quick check
Key takeaways
- end() is one past the last element, so never read it.
- Algorithms work on a pair of iterators.
- Prefer a standard algorithm to a hand-written loop.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.