erase and the range-based loop
s.erase(x) removes x from the set s. It returns how many values it removed: 1 if x was in the set and 0 if it wasn't. Erasing a value that isn't there is not an error; nothing happens.
unordered_set<string> colors = {"red", "blue", "green"};
colors.erase("blue");
colors.erase("pink"); // not in it, nothing happens
for (string color : colors) {
cout << color << endl; // red and green, in any order
}
for (string x : s) visits each value of the set once. The order isn't fixed: that is the "unordered" in unordered_set. So loop over a set when the order doesn't matter, for a total, a count or a check.
The loop above copies each value into color. For big values such as long strings, const string& color avoids the copy. The result is the same.
Two other ways to shrink a set are worth knowing by name. s.clear() removes every value at once, and s.size() afterwards is 0. s.empty() tells you whether anything is left. Neither is needed for this lesson, which only removes two named words. Erase is also the right tool for "take this name off the list" jobs, where the list is a set of names.
Traps
- Don't erase while looping over the same set. Erasing a value the loop is standing on invalidates the loop's position, so the program may crash or skip values. Erase first, then loop. If you need to remove values found during a loop, collect them in a vector and erase them after the loop.
- Don't depend on the order. Printing the values shows them in whatever order the set keeps them. Sums and counts don't depend on it.
- Use the return value if you need it.
erasereturns 1 the first time and 0 the second time you erase the same value.
Here is that two-pass shape on the words cat, tree, qqq, moon and zzz, where the unwanted words are the three-letter words that start with a doubled letter:
vector<string> banned;
for (string w : found) {
if (w.size() == 3 && w[0] == w[1]) {
banned.push_back(w);
}
}
for (string w : banned) {
found.erase(w);
}
The first loop only reads found. The second loop reads banned and erases from found, which is a different container, so nothing is invalidated. Afterwards found holds 3 words.
The lesson's task
In a word game, found holds the words cat, tree, sun, qqq and moon. The words "qqq" and "zzz" aren't allowed. Remove both, then add up one point per letter.
- Call
erasewith"qqq"and then with"zzz". The second call removes nothing, because the player never found it. Four words are left. - Loop over
foundwith astringnamedword. - Inside the loop, add
word.size()toscore.
class Solution {
public:
void mainFunction() {
vector<string> words = {"cat", "tree", "sun", "qqq", "moon"};
unordered_set<string> found(words.begin(), words.end());
found.erase("qqq");
found.erase("zzz");
cout << found.size() << " words" << endl;
int score = 0;
for (string word : found) {
score += word.size();
}
cout << score << " points" << endl;
}
};
The output is:
4 words
14 points
The four remaining words have 3, 4, 3 and 4 letters, which add up to 14, whichever order the loop visits them in. word.size() is an unsigned number, and adding it to an int is fine for small lengths like these.
Key takeaways
erase(x)returns 1 or 0 and never fails for a missing value.- A range-based
forvisits every value once, in no fixed order. - Erase before looping, not during it.
