Skip to Content
Course content

82: Practice Exercise: Solving Problems with STL Containers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you're staring at a problem, the hardest part isn't usually the syntax—it's picking the right container. If you pick a vector when you needed a set, your code might work fine for ten elements, but it'll crawl to a halt when you hit ten thousand. I want to show you how I actually think through this process by building a word frequency counter. We're going to take a big chunk of text and figure out which words appear most often.

The Naive Approach

My first instinct for "a list of things" is always std::vector. It's the default for a reason. Let's see what happens if we just store every word we encounter and then search through them.

std::vector<std::string> words = {"apple", "banana", "apple", "orange", "banana", "apple"};
std::vector<int> counts;

for (const auto& w : words) {
    auto it = std::find(words.begin(), words.end(), w);
    // ... wait, this is getting messy. 
    // I have to track the index of the word in the 'words' vector 
    // and then update a corresponding index in the 'counts' vector.
}

Stop right there. I can already tell this is a disaster. I'm doing a linear search (std::find) for every single word. If I have a million words, I'm potentially doing a million searches across a million elements. That's $O(n^2)$ complexity. My laptop fans would start screaming. We need a way to associate a word directly with its count without scanning a list.

Switching to an Associative Map

This is where std::map comes in. It's designed exactly for this: mapping a key (the word) to a value (the count). Let's try that.

std::map<std::string, int> wordCounts;
for (const auto& w : words) {
    wordCounts[w]++;
}

That's infinitely cleaner. In C++, the [] operator for maps is incredibly convenient; if the key doesn't exist, it value-initializes the int to 0 and then increments it. I've just turned my search time from $O(n)$ to $O(\log n)$.

But here's the catch: std::map keeps its keys sorted alphabetically. If I iterate through wordCounts now, I'll get "apple", then "banana", then "orange". That's great if I'm building a dictionary, but I don't actually care about alphabetical order; I care about frequency. And the map's internal red-black tree structure adds a bit of overhead we might not need.

Trading Order for Speed

Since I don't need the words to be sorted as I count them, I'm going to swap std::map for std::unordered_map. The API is almost identical, but it uses a hash table under the hood.

std::unordered_map<std::string, int> wordCounts;
for (const auto& w : words) {
    wordCounts[w]++;
}

Now we're looking at $O(1)$ average time complexity for insertions and lookups. For a massive dataset, this is a massive win. But we've hit a new wall: how do I find the most frequent words? You can't sort an unordered_map by its values because the "order" is determined by the hash of the keys, not the values you've stored.

Getting the Top Results

To solve this, I have to move the data again. I can't sort the map, so I'll dump the map's contents into a std::vector of pairs. Once it's in a vector, I can use std::sort with a custom lambda to sort by the frequency.

std::vector<std::pair<std::string, int>> sortedWords(wordCounts.begin(), wordCounts.end());

std::sort(sortedWords.begin(), sortedWords.end(), [](const auto& a, const auto& b) {
    return a.second > b.second; // Sort descending by count
});

I've essentially used three different containers to solve one problem: unordered_map for efficient counting, vector for a sortable structure, and pair to keep the word and its count glued together. This "pipeline" approach—collecting in one container and refining in another—is a very common pattern in professional C++ development.




📋 Practical Task

Exercise: The Log File Error Analyzer

You are tasked with building a tool that analyzes a server log. The log is a list of error codes (strings like "ERR_404", "ERR_500", "ERR_403"). Your goal is to identify the "Top 3" most frequent error codes to help the DevOps team prioritize fixes.

Requirements:

  • Create a std::vector<std::string> containing at least 20 error codes (ensure some are repeated multiple times).
  • Use a std::unordered_map to count the occurrences of each unique error code.
  • Transfer the results into a std::vector<std::pair<std::string, int>>.
  • Sort the vector in descending order based on the count.
  • Print only the top 3 most frequent error codes and their counts to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.