14.7. The map class¶
A map refers to any data structure that ‘maps’ keys to values.
The map
class is arguably the most popular container in the STL
after vector
.
All the containers discussed so far focused on storing 1 thing.
That is, each stores values of a single type.
Maps add a new wrinkle.
A map
stores pairs of things.
Traditionally, the pair stored is referred to as a key-value pair.[1]
Nearly every programming language provides some kind of map
implementation.
Some languages use the terms associative array or dictionary List,
but structurally, they are very similar.
Values are retrieved from a map
using the key.
Each key must be unique.
In other words, keys are members of a set
.
Like a std::set,
adding a second node with the same key replaces the old value.
Unlike a std::set
,
a std::map provides the map::operator[].
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> name_counts {{"Alice", 27},
{"Bob", 3},
{"Clara", 1}};
for (const auto& kvp : name_counts) {
std::cout << kvp.first << ": "
<< kvp.second << '\n';
}
name_counts["Bob"] = 42; // update existing value
name_counts["Darla"] = 9; // insert a new value
// get map values
std::cout << "Alice is " << name_counts.find("Alice")->second << '\n';
// or get the key iterator, then print
auto it = name_counts.find("Alice");
std::cout << "Alice is " << it->second << '\n';
std::cout << "Bob is " << name_counts.at("Bob") << '\n';
std::cout << "Darla is " << name_counts["Darla"] << '\n';
}
14.7.1. Selected map functions¶
- Access and assignment
- Capacity
- Modifiers
- Lookup
count, find, equal_range, upper_bound and lower_bound
For large data sets, the lookup functions in a map
are faster than their
counterparts in a sequential container such as vector
.
Note
There is no push_back()
for a map.
The map
decides where elements go, not you.
All access requires either knowing the key or having an iterator.
14.7.2. Map structure¶
Internally, a map
is a sorted complete binary tree.
(Technically it is often implemented as a Red-black tree).
Each node in the tree is a std::pair.
All nodes are sorted by their keys.
Sorting is managed using operator<
by default,
but this can be configured in the map constructor
using a custom compare function or class, just as with a set
.
#include <iostream>
#include <map>
#include <set>
#include <string>
using std::string;
void print (std::set<string> keys) {
for (const auto& key: keys) {
std::cout << key << ' ';
}
}
int main() {
std::map<string, int> inventory {
{"apple", 12},
{"kiwi", 4},
{"lemon", 1},
{"pear", 4},
{"peach", 4},
{"grape", 100},
{"cocoa", 3},
};
std::set<string> inventory_keys;
// extract keys from the inventory map
for (const auto& i: inventory) {
auto result = inventory_keys.insert(i.first);
if (!result.second) std::cout << "no insertion\n";
}
std::cout << "All fruit keys:\n";
print (inventory_keys);
std::set<string> keys;
auto it = inventory.upper_bound("kiwi");
while(it != inventory.end()) {
auto result = keys.insert(it->first);
if (!result.second) std::cout << "no insertion\n";
++it;
}
std::cout << "\n\nAll fruit keys greater than 'kiwi':\n";
print (keys);
}
and produces:
All fruit keys:
apple cocoa grape kiwi lemon peach pear
All fruit keys greater than 'kiwi':
lemon peach pear
Using a customer comparator, we can store map items in reverse order:
// custom comparator
#include <functional> // std::greater
#include <iostream>
#include <map>
#include <string>
// print inventories with different custom comparators
template <class Comparator>
void print (const string title, const std::map<string, int, Comparator>& x) {
std::cout << title;
for (const auto& kvp: x) {
std::cout << kvp.first << ", " << kvp.second << '\n';
}
}
int main() {
std::map<string, int> inventory {
{"apple", 12},
{"kiwi", 4},
{"lemon", 1},
{"pear", 4},
{"peach", 4},
{"grape", 100},
{"cocoa", 3},
};
print ("Initial inventory:\n", inventory);
// define a reverse ordered map
// a lambda is not the best choice here
const auto greater_than = [] (string lhs, string rhs) { return lhs > rhs;};
std::map<string, int, decltype(greater_than)> reverse_inventory1 (greater_than);
// but it works
for (auto& i: inventory) {
reverse_inventory1.insert(i);
}
print ("\n\nReverse inventory using lambda:\n", reverse_inventory1);
// STL provides many basic operations wrapped in a std::function
std::map<string, int, std::greater<string>> reverse_inventory2;
for (auto& i: inventory) {
reverse_inventory2.insert(i);
}
print ("\nReverse inventory using std::greater:\n", reverse_inventory2);
return 0;
}
which produces:
Initial inventory:
apple, 12
cocoa, 3
grape, 100
kiwi, 4
lemon, 1
peach, 4
pear, 4
Reverse inventory using lambda:
pear, 4
peach, 4
lemon, 1
kiwi, 4
grape, 100
cocoa, 3
apple, 12
Reverse inventory using std::greater:
pear, 4
peach, 4
lemon, 1
kiwi, 4
grape, 100
cocoa, 3
apple, 12
14.7.3. Variations on std::map¶
The STL provides 3 alternate forms of map class:
- container:multimap
A
map
in which duplicate keys are allowed.- unordered_map
A
map
of unique key-value pairs stored based on the key object hash function. Added in C++11.- unordered_multimap
An
unordered_map
in which duplicate keys are allowed.
More to Explore
Red-black tree on Wikipedia