C++ Map Explained with Examples

The C++ map is a powerful tool in your toolkit as a C++ developer. Maps allow you to store and retrieve elements as key-value pairs, providing fast lookups by key. In this post, we‘ll dive into what C++ maps are, how to use them, and see examples of maps in action.

What is a C++ map?

A map is an associative container that stores elements formed by a key and a mapped value. The key acts as an index into the map, allowing fast O(log n) lookups by key. Maps are part of the C++ Standard Template Library (STL).

The main features and benefits of C++ maps include:

  • Fast search, insertion, and deletion of elements (O(log n) time complexity)
  • Keys are unique and stored in sorted order
  • Maps manage their own storage, dynamically sizing as elements are added/removed
  • Accessing elements by key is intuitive and efficient
  • Iterators allow accessing elements in sorted key order

Maps are typically implemented as binary search trees under the hood, giving them their logarithmic time complexity for common operations.

Creating a map

To create a map in C++, you must #include the

header file. Then you can declare a map variable, specifying the data types for the key and value inside angle brackets:

#include <map>
using namespace std;

map<string, int> employeeSalaries;

This declares a map called employeeSalaries where the keys are strings (employee names) and the values are integers (the salaries).

The key type for a map must be a comparable data type, as keys are kept in sorted order for fast lookups. Standard data types like int, double, string, etc. can all be used as keys.

You can also initialize a map with values using an initializer list:

map<char, int> letterCounts = {{‘a‘, 10}, {‘b‘, 3}, {‘c‘, 14}};

Inserting elements

There are several ways to insert key-value pairs into a map.

The most direct way is to use the [] operator, treating the map like an associative array:

employeeSalaries["John"] = 50000;
employeeSalaries["Sarah"] = 75000;
employeeSalaries["Tom"] = 60000;

You can also use the insert() method, passing in a pair object:

employeeSalaries.insert(make_pair("Chris", 55000));
employeeSalaries.insert(pair<string,int>("Lisa", 68000));  

If an element with the given key already exists, the [] operator will overwrite the value, while insert() will not modify the existing element.

Accessing elements

To retrieve a value from a map by its key, you can again use the [] operator:

int salary = employeeSalaries["Sarah"];
cout << "Sarah‘s salary is: " << salary << endl;

However, be cautious with [], as it will create a new element with the specified key if it doesn‘t exist. To safely check if a key exists and get its value, use the find() method instead:

auto it = employeeSalaries.find("John");
if (it != employeeSalaries.end()) {
    cout << "John‘s salary is: " << it->second << endl;
} else {
    cout << "Employee not found." << endl;  
}

find() returns an iterator to the element if found, or the end iterator (indicating no match) otherwise. The expression it->second gives the value for the key-value pair.

Iterating through a map

Maps provide iterators for traversing through elements in sorted key order. You can use a range-based for loop for a simple iteration:

for (auto& pair : employeeSalaries) {
    cout << pair.first << " earns " << pair.second << endl;
}

Or you can use a regular iterator:

for (map<string,int>::iterator it = employeeSalaries.begin(); it != employeeSalaries.end(); ++it) {
    cout << it->first << " earns " << it->second << endl;
}  

If you only need the keys or values, you can iterate using the dedicated key/value iterators:

for (map<string,int>::key_iterator it = employeeSalaries.key_begin(); it != employeeSalaries.key_end(); ++it) {
    cout << *it << endl;
}

Removing elements

To remove an element from a map, you can use the erase() method, passing in either an iterator or a key value:

// Erase by iterator  
auto it = employeeSalaries.find("Tom");
if (it != employeeSalaries.end()) {
    employeeSalaries.erase(it);
}

// Erase by key
employeeSalaries.erase("Chris");

Getting the size of a map

To get the number of elements currently in a map, use the size() method:

cout << "Employee records: " << employeeSalaries.size() << endl;

You can check if a map is empty using the empty() method, which returns a bool:

if (employeeSalaries.empty()) {
    cout << "No employee records!" << endl;
}  

Maps vs unordered maps

C++ also provides an unordered_map class, which is similar to map but with different performance characteristics.

The key differences are:

  • map keeps elements sorted by key, allowing key-ordered iteration
  • unordered_map does not maintain key order, but provides faster O(1) lookups on average
  • map requires a comparison function for keys (less by default)
  • unordered_map requires a hash function for keys

So if you need to maintain key order or want key-ordered iteration, use map. If you need the fastest possible lookups and don‘t care about key order, use unordered_map.

Example use cases

Some common problems that maps are well-suited for:

  • Counting the frequency of items in a list:
vector<string> words = {"apple", "banana", "apple", "cherry", "banana"};
map<string, int> wordCounts;

for (const string& word : words) {
    wordCounts[word]++; 
}

for (auto& pair : wordCounts) {
    cout << pair.first << ": " << pair.second << endl;
}

Output:

apple: 2
banana: 2 
cherry: 1
  • Grouping items by a common property:
struct Student {
    string name;
    int grade;  
};

vector<Student> students = {{"John", 12}, {"Sarah", 11}, {"Bob", 12}, {"Alice", 11}, {"Tom", 12}};  
map<int, vector<string>> studentsByGrade;

for (const Student& s : students) {
    studentsByGrade[s.grade].push_back(s.name);
}

for (auto& pair : studentsByGrade) {
    cout << "Grade " << pair.first << ": ";
    for (const string& name : pair.second) {
        cout << name << " ";  
    }
    cout << endl;
}

Output:

Grade 11: Sarah Alice
Grade 12: John Bob Tom 
  • Creating a phone book:
map<string, string> phoneBook = {  
    {"John", "123-456-7890"},
    {"Sarah", "987-654-3210"}, 
    {"Tom", "111-222-3333"}
};

string name;
cout << "Enter name to look up: ";
cin >> name;

auto it = phoneBook.find(name);  
if (it != phoneBook.end()) {
    cout << it->first << "‘s number is: " << it->second << endl;
} else {
    cout << "Name not found in phone book." << endl;
}

Best practices

A few tips for getting the most out of maps in C++:

  • Use clear, descriptive names for the key and value types to make your code more readable.
  • Prefer accessing elements with find() instead of [] to avoid accidentally creating new elements.
  • Use emplace() and emplace_hint() when possible to avoid unnecessary copies during insertion.
  • Pass maps by reference to functions to avoid expensive copies.
  • Use const references for read-only access to map elements.
  • Consider reserving space in advance with reserve() if you know the size the map will grow to.

Conclusion

Maps are an essential part of the C++ standard library, providing a powerful, efficient way to store and retrieve key-value pairs. Whether you‘re a beginner or an experienced C++ developer, taking the time to master maps is sure to pay off in your projects.

The most important things to remember about maps:

  1. Maps store key-value pairs, allowing fast lookups by unique, sorted keys
  2. Use [] to access values by key, and find() to safely check for key existence
  3. Insertions and deletions are O(log n), lookups are O(log n) on average
  4. Use maps when you need sorted keys, and unordered_map when you need the fastest possible lookups

I hope this post has given you a solid understanding of C++ maps and how to use them effectively. Thanks for reading!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *