-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
82: Practice Exercise: Solving Problems with STL Containers
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_mapto 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.
There are no comments for now.