Why custom sorting
Called with just begin() and end(), sort puts numbers from small to large and strings from A to Z. Often you need another order: the highest price first, the shortest word first, or names ordered by their last letter. For that, sort takes a third argument, your own rule.
The rule is a lambda: a small function with no name, written right where you need it. It starts with [], then come the parameters and the body in braces. The lambda gets two items, a and b, and returns true when a must go before b. sort calls it again and again, on two items at a time, until the vector is in order.
One rule
vector<int> scores = {70, 95, 82};
sort(scores.begin(), scores.end(), [](int a, int b) { return a > b; });
The vector now holds 95 82 70. Bigger first means returning a > b; smaller first means a < b. For strings, a.size() < b.size() puts the shortest word first. A parameter written as const string& a lets the lambda read a string without copying it.
Two rules
For two rules, check the first rule, and use the second one only on a tie:
vector<int> nums = {25, 13, 5, 41, 33};
sort(nums.begin(), nums.end(),
[](int a, int b) {
if (a % 10 != b % 10) {
return a % 10 < b % 10;
}
return a < b;
});
The numbers come out as 41 13 33 5 25. The first rule compares last digits. Numbers with the same last digit (13 and 33, or 5 and 25) then go from small to large.
Things to know
- Use
<or>in the lambda, never<=or>=. Two equal items must givefalseboth ways, orsortcan misbehave and even crash. sortdoesn't keep equal items in their old order. Add a second rule when ties matter.- It changes the vector itself. To keep the original, sort a copy. With
words = {"pear", "fig", "apple"}, the linevector<string> copy = words;makes one. After sortingcopyby size,wordsstill readspear fig appleandcopyreadsfig pear apple. - In a file of your own, add
#include <algorithm>,<vector>and<string>for these examples.
The lesson's task
A pet shop has toy prices {12, 5, 30, 8} and pets {"Rex", "Luna", "Bella", "Max", "Bo", "Coco"}. Sort the prices from high to low. Sort the names by their last letter in alphabetical order, with names that share a last letter going from A to Z.
- For the prices, call
sortwithprices.begin(),prices.end()and a lambda that returnsa > b. - For the names, the lambda needs two rules, so give it a body in braces, closed by
});. The last letter sits at the last index, one less than the size:a[a.size() - 1]. Store the two last letters inlastAandlastB. - If they differ, return
lastA < lastB. On twochars,<is true when the first letter comes first in the alphabet. - Otherwise return
a < b. Max and Rex both end in x, and this puts Max first.
class Solution {
public:
void mainFunction() {
vector<int> prices = {12, 5, 30, 8};
vector<string> names = {"Rex", "Luna", "Bella", "Max", "Bo", "Coco"};
sort(prices.begin(), prices.end(), [](int a, int b) { return a > b; });
for (int price : prices) {
cout << price << endl;
}
sort(names.begin(), names.end(), [](const string& a, const string& b) {
char lastA = a[a.size() - 1];
char lastB = b[b.size() - 1];
if (lastA != lastB) {
return lastA < lastB;
}
return a < b;
});
for (string name : names) {
cout << name << endl;
}
}
};
The prices print as 30, 12, 8, 5. The names end in a (Bella, Luna), o (Bo, Coco) and x (Max, Rex), so they print as:
Bella
Luna
Bo
Coco
Max
Rex
Common mistakes
- Mixing up the direction.
>puts big values first,<puts small values first. - Skipping the tie rule. Without it, names with the same last letter can come out in any order.
- Using
<=. An item that "goes before itself" breaks the rulessortrelies on. - Taking the last letter of an empty string.
size() - 1is unsigned, so on an empty string it wraps to a huge number. Here every name has letters.
Key takeaways
sort(v.begin(), v.end(), lambda)sortsvin place; the lambda returnstruewhen its first argument goes first.- For several rules, return early on the first rule that decides, and fall through to the next rule on a tie.
- Use strict comparisons, and sort a copy when you need to keep the original.
