Associative containers differ in a fundamental respect from the sequential containers: Elements in an associative container are stored and retrieved by a key, in contrast to elements in a sequential container, which are stored and accessed sequentially by their position within the container
Associative containers support efficient lookup and retrieval by a key. The two primary associative-container types are map and set. The elements in a map are keyvalue pairs: The key serves as an index into the map, and the value represents the data that are stored and retrieved. A set contains only a key and supports efficient queries to whether a given key is present
In general, a set is most useful when we want to store a collection of distinct values efficiently, and a map is most useful when we wish to store (and possibly modify) a value associated with each key
We might use a set to hold words that we want to ignore when doing some kind of text processing. A dictionary would be a good use for a map: The word would be the key, and its definition would be the value
An object of the map or set type may contain only a single element with a given key. There is no way to add a second element with the same key. If we need to have multiple instances with a single key, then we can use multimap or multi set, which do allow multiple elements with a given key
|
Section 10.1 Preliminaries: the pair Type
- Before we look at the associative containers, we need to know about a simple companion library type named pair, which is defined in the utility header.
- Creating and Initializing pairs
- A pair holds two data values. Like the containers, pair is a template type. Unlike the containers we've seen so far, we must supply two type names when we create a pair: A pair holds two data members, each of which has the corresponding named type
- When we create pair objects with no initializer, the default constructor value-initializes the members
- The pair type can be unwieldy to type, so when we wish to define a number of objects of the same pair type, it is convenient to use a typedef
Table 10.2. Operations on pairs
pair<T1, T2> p1;
Create an empty pair with two elements of types T1 and T2. The elements are value-initialized (Section 3.3.1, p. 92).
pair<T1, T2> p1(v1, v2);
Create a pair with types T1 and T2 initializing the first member from v1 and the second from v2.
make_pair(v1, v2)
Creates a new pair from the values v1 and v2. The type of the pair is inferred from the types of v1 and v2.
p1 < p2
Less than between two pair objects. Less than is defined as dictionary ordering: Returns true if p1.first < p2.first or if !(p2.first < p1.first) && p1.second < p2.second.
p1 == p2
Two pairs are equal if their first and second members are respectively equal. Uses the underlying element == operator.
p.first
Returns the (public) data member of p named first.
p.second
Returns the (public) data member of p named second.
- Operations on pairs
- Unlike other library types, the pair class gives us direct access to its data members: Its members are public
- Generating a New pair
- In addition to the constructors, the library defines the make_pair function, which generates a new pair from its two arguments. We might use this function to make a new pair to assign to an existing pair
// EXE-10.1.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <utility>
#include <string>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
typedef pair<string, string> Author;
if("Creating and Initializing pairs")
{
pair<string, string> anon; // holds two strings
pair<string, int> word_count; // holds a string and an int
pair< string, vector<int> > line; // holds string and vector<int>
pair<string, string> author("James", "Joyce");
Author proust("Marcel", "Proust");
Author joyce("James", "Joyce");
}
if("Operations on pairs")
{
Author author("James","Joyce");
string firstBook;
// access and test the data members of the pair
if (author.first == "James" && author.second == "Joyce")
firstBook = "Stephen Hero";
}
if("Generating a New pair")
{
pair<string, string> next_auth;
string first, last;
while (cin >> first >> last) {
// generate a pair from first and last
next_auth = make_pair(first, last);
// use pair constructor to make first and last into a pair
//next_auth = pair<string, string>(first, last);
// process next_auth...
}
}
int screen_hold;
cin >> screen_hold;
return 0;
}
Section 10.2 Associative Containers
- The operations common to sequential and associative containers are
- The first three constructors described in Table 9.2 (p. 307):
C<T> c; // creates an empty container
// c2 must be same type as c1
C<T> c1(c2); // copies elements from c2 into c1
// b and e are iterators denoting a sequence
C<T> c(b, e); // copies elements from the sequence into c- The relational operations described in Section 9.3.4 (p. 321
- The begin, end, rbegin, and rend operations of Table 9.6 (p. 317
- The typedefs listed in Table 9.5 (p. 316). Note that for map, the value_type is not the same as the element type. Instead, value_type is a pair representing the types of the keys and associated values. Section 10.3.2 (p. 361) explains the typedefs for maps in more detail
- The swap and assignment operator described in Table 9.11 (p. 329). Associative containers do not provide the assign functions
- The clear and erase operations from Table 9.10 (p. 326), except that the erase operation on an associative container returns void
- The size operations in Table 9.8 (p. 324) except for resize, which we cannot use on an associative container
- The first three constructors described in Table 9.2 (p. 307):
- Elements Are Ordered by Key
- The associative container types define additional operations beyond the ones just listed. They also redefine the meaning or return type of operations that are in common with the sequential containers. The differences in these common operations reflect the use of keys in the associative containers
- There is one important consequence of the fact that elements are ordered by key: When we iterate across an associative container, we are guaranteed that the elements are accessed in key order, irrespective of the order in which the elements were placed in the container
- The associative container types define additional operations beyond the ones just listed. They also redefine the meaning or return type of operations that are in common with the sequential containers. The differences in these common operations reflect the use of keys in the associative containers
Section 10.3 The map Type
- A map is a collection of keyvalue pairs. The map type is often referred to as an associative array: It is like the built-in array type, in that the key can be used as an index to fetch a value. It is associative in that values are associated with a particular key rather than being fetched by position in the array
- 10.3.1. Defining a map
- To use a map, we must include the map header
Table 10.3. Constructors for map
map<k, v> m;
Create an empty map named m with key and value types k and v.
map<k, v> m(m2);
Create m as a copy of m2; m and m2 must have the same key and value types.
map<k, v> m(b, e);
Create m as a copy of the elements from the range denoted by iterators b and e. Elements must have a type that can be converted to pair<const k, v>.
- Constraints on the Key Type
- Whenever we use an associative container, its keys have not only a type, but also an associated comparison function. By default, the library uses the < operator for the key type to compare the keys
- Whichever comparison function we use must define a strict weak ordering over the key type. We can think of a strict weak ordering as "less than," although we might choose to define a more complicated comparison function. However we define it, such a comparison function must always yield false when we compare a key with itself. Moreover, if we compare two keys, they cannot both be "less than" each other, and if k1 is "less than" k2, which in turn is "less than" k3, then k1 must be "less than" k3. If we have two keys, neither of which is "less than" the other, the container will treat them as equal. When used as a key to a map, either value could be used to access the corresponding element
- In practice, what's important is that the key type must define the < operator and that that operator should "do the right thing."
- The key type needs to support only the < operator. There is no requirement that it support the other relational or equality operators
- Whenever we use an associative container, its keys have not only a type, but also an associated comparison function. By default, the library uses the < operator for the key type to compare the keys
- To use a map, we must include the map header
- 10.3.2. Types Defined by map
- The elements of a map are keyvalue pairs That is, each element has two parts: its key and the value associated with that key. The value_type for a map reflects this fact. This type is more complicated than those we've seen for other containers: value_type is a pair that holds the key and value of a given element. Moreover, the key is const
Table 10.4. Types Defined by the map Class
map<K, V>::key_type
The type of the keys used to index the map.
map<K, V>::mapped_type
The type of the values associated with the keys in the map.
map<K, V>::value_type
A pair whose first element has type const
map<K, V>::key_type and second has type
map<K, V>::mapped_type.
- When learning the map interface, it is essential to remember that the value_type is a pair and that we can change the value but not the key member of that pair
- Dereferencing a map Iterator Yields a pair
- When we dereference an iterator, we get a reference to a value of the container's value_type
- Additional map Typedefs
- The map class defines two additional types, key_type and mapped_type, that let us access the type of either the key or the value
- The map class defines two additional types, key_type and mapped_type, that let us access the type of either the key or the value
- The elements of a map are keyvalue pairs That is, each element has two parts: its key and the value associated with that key. The value_type for a map reflects this fact. This type is more complicated than those we've seen for other containers: value_type is a pair that holds the key and value of a given element. Moreover, the key is const
- 10.3.3. Adding Elements to a map
- We can do so either by using the insert member or by fetching an element using the subscript operator and then assigning a value to the element returned. In both cases, the fact that there can be only a single element for a given key affects the behavior of these operations
- The following steps take place
- word_count is searched for the element whose key is Anna. The element is not found.
- A new keyvalue pair is inserted into word_count. The key is a const string holding Anna. The value is value initialized, meaning in this case that the value is 0.
- The new keyvalue pair is inserted into word_count.
- The newly inserted element is fetched and is given the value 1.
- word_count is searched for the element whose key is Anna. The element is not found.
- Subscripting a map behaves quite differently from subscripting an array or vector: Using an index that is not already present adds an element with that index to the map
- As with other subscript operators, the map subscript takes an index (that is, a key) and fetches the value associated with that key. When we look for a key that is already in the map, then the behavior is the same for a map subscript or a vector subscript: The value associated with the key is returned. For maps only, if the key is not already present, a new element is created and inserted into the map for that key. The associated value is value-initialized: An element of class type is initialized using the default constructor for the element type; a built-in type is initialized to 0
- Using the Value Returned from a Subscript Operation
- Unlike vector or string, the type returned by map subscript operator differs from the type obtained by dereferencing a map iterator
- Programming Implications of the Subscript Behavior
- We can do so either by using the insert member or by fetching an element using the subscript operator and then assigning a value to the element returned. In both cases, the fact that there can be only a single element for a given key affects the behavior of these operations
- 10.3.4. Subscripting a map
- 10.3.5. Using map::insert
- The insert members operate similarly to the operations on sequential containers (Section 9.3.3, p. 318), with one important caveat: We must account for the effect of the key.
- The key impacts the argument types: The versions that insert a single element take a value that is a keyvalue pair. Similarly, for the version that takes an iterator pair, the iterators must refer to elements that are keyvalue pairs. The other difference is the return type from the version of insert that takes a single value, which we will cover in the remainder of this section
- Using insert Instead of Subscripting
- Alternatively, we could insert the element directly by using the syntactically more intimidating insert member
Table 10.5. insert Operations on maps
e is a value of the value_type for m. If the key(e.first) is not in m, inserts a new element with value e.second. If the key is in m, then m is unchanged. Returns a pair containing a map iterator referring to the element with key e.first and a bool indicating whether the element was inserted or not.
m.insert(beg, end)
beg and end are iterators that denote a range of values that are keyvalue pairs with the same type as m's value_type. For each element in the range, if the given key is not already in m, it inserts the key and its associated value into m. Returns void.
m.insert(iter, e)
e is a value of the value_type for m. If the key(e.first) is not in m, inserts the new element using the iterator iter as a hint for where to begin the search for where the new element should be stored. Returns an iterator that refers to the element in m with given key.
// if Anna not already in word_count, inserts new element with value 1
word_count.insert(map<string, int>::value_type("Anna", 1)); - Remember that value_type is a synonym for the type pair<const K, V>, where K is the key type and V is the type of the associated value. The argument to insert constructs a new object of the appropriate pair type to insert into the map
- By using insert, we can avoid the extraneous initialization of the value that happens when we insert a new map element as a side-effect of using a subscript
- Alternatively, we could insert the element directly by using the syntactically more intimidating insert member
- Testing the Return from insert
- However, the version of insert that takes a single keyvalue pair does return a value. That value is a pair that contains an iterator that refers to the element in the map with the corresponding key, and a bool that indicates whether the element was inserted
- Unwinding the Syntax
- The insert members operate similarly to the operations on sequential containers (Section 9.3.3, p. 318), with one important caveat: We must account for the effect of the key.
- 10.3.6. Finding and Retrieving a map Element
- 10.3.7. Erasing Elements from a map
- 10.3.8. Iterating across a map
- 10.3.9. A Word Transformation Map
Section 10.4 The set Type
1. In contrast, a set is simply a collection of keys
2. A set is most useful when we simply want to know whether a value is present
3. With two exceptions, set supports the same operations as map
o All the common container operations listed in Section 10.2 (p. 358).
o The constructors described in Table 10.3 (p. 360).
o The insert operations described in Table 10.5 (p. 365).
o The count and find operations described in Table 10.6 (p. 367).
o The erase operations described in Table 10.7 (p. 369).
4. The exceptions are that set does not provide a subscript operator and does not define mapped_type. In a set, the value_type is not a pair; instead it and key_type are the same type. They are each the type of the elements stored in the set
5. As with map, the keys of a set must be unique and may not be changed
6. 10.4.1. Defining and Using sets
1. To use a set, we must include the set header. The operations on sets are essentially identical to those on maps
2. Adding Elements to a set
3. Fetching an Element from a set
1. There is no subscript operator on sets
2. To fetch an element from a set by its key, we use the find operation. If we just want to know whether the element is present, we could also use count, which returns the number of elements in the set with a given key
3. Just as we cannot change the key part of a map element, the keys in a set are also const
4.
4.
7. 10.4.2. Building a Word-Exclusion Set
8.
Section 10.5 The multimap and multiset Types
- 1. The types multiset and a multimap allow multiple instances of a key.
- 2. The multimap and multiset types are defined in the same headers as the corresponding single-element versions: the map and set headers, respectively
- 3. The operations supported by multimap and multiset are the same as those on map and set, respectively, with one exception: multimap does not support subscripting
- 4. We cannot subscript a multimap because there may be more than one value associated with a given key
- 5. The insert operations described in Table 10.5 (p. 365) and the erase operations described in Table 10.7 (p. 369) are used to add and remove elements of a multimap or multiset
- 6. Because keys need not be unique, insert always adds an element
- 7. The version of erase that takes a key removes all elements with that key. It returns a count of how many elements were removed
- 8. The versions that take an iterator or an iterator pair remove only the indicated element(s).
- 10.5.1. Adding and Removing Elements
- We noted that maps and sets store their elements in order
- The multimap and multiset types do so as well. As a result, when a multimap or multiset has multiple instances of a given key, those instances will be adjacent elements within the container
- We are guaranteed that iterating across a multimap or multiset returns all the elements with a given key in sequence
- We noted that maps and sets store their elements in order
- 10.5.2. Finding Elements in a multimap or multiset
- It turns out that there are three strategies we might use to find and print all the books by a given author. Each of these strategies relies on the fact that we know that all the entries for a given author will be adjacent within the multimap
- It turns out that there are three strategies we might use to find and print all the books by a given author. Each of these strategies relies on the fact that we know that all the entries for a given author will be adjacent within the multimap
- Using find and count
- A Different, Iterator-Oriented Solution
- They can be used with (plain) maps or sets but are most often used with multimaps or multisets. Each of these operations takes a key and returns an iterator
- Calling lower_bound and upper_bound on the same key yields an iterator range (Section 9.2.1, p. 314) that denotes all the elements with that key. If the key is in the container, the iterators will differ: the one returned from lower_bound will refer to the first instance of the key, whereas upper_bound will return an iterator referring just after the last instance. If the element is not in the multimap, then lower_bound and upper_bound will return equal iterators; both will refer to the point at which the key could be inserted without disrupting the order
- The iterator returned from lower_bound may or may not refer to an element with the given key. If the key is not in the container, then lower_bound refers to the first point at which this key could be inserted while preserving the element order within the container
- These operations say nothing about whether the key is present. The important point is that the return values act like an iterator range
- If there is no element for this key, then lower_bound and upper_bound will be equal: They will both refer to the same element or they will both point one past the end of the multimap. They both will refer to the point at which this key could be inserted while maintaining the container order
Table 10.8. Associative Container Operations Returning Iterators
Returns an iterator to the first element with key not less than k.
m.upper_bound(k)
Returns an iterator to the first element with key greater than k.
m.equal_range(k)
Returns a pair of iterators.
The first member is equivalent to m.lower_bound(k) and second is equivalent to m.upper_bound(k).
- They can be used with (plain) maps or sets but are most often used with multimaps or multisets. Each of these operations takes a key and returns an iterator
- The equal_range Function
- Instead of calling upper_bound and lower_bound, we can call equal_range. This function returns a pair of iterators. If the value is present, then the first iterator refers to the first instance of the key and the second iterator refers one past the last instance of the key. If no matching element is found, then both the first and second iterator refer to the position where this key could be inserted
- Instead of calling upper_bound and lower_bound, we can call equal_range. This function returns a pair of iterators. If the value is present, then the first iterator refers to the first instance of the key and the second iterator refers one past the last instance of the key. If no matching element is found, then both the first and second iterator refer to the position where this key could be inserted
Section 10.6 Using Containers: Text-Query Program
- To conclude this chapter, we'll implement a simple text-query program
- 10.6.1. Design of the Query Program
- A good way to start the design of a program is to list the program's operations. Knowing what operations we need to provide can then help us see what data structures we'll need and how we might implement those actions
- It must allow the user to indicate the name of a file to process. The program will store the contents of the file so that we can display the original line in which each word appears
- The program must break each line into words and remember all the lines in which each word appears. When it prints the line numbers, they should be presented in ascending order and contain no duplicates
- The result of querying for a particular word should be the line numbers on which that word appeared
- To print the text in which the word appeared, it must be possible to fetch the line from the input file corresponding to a given line number
- It must allow the user to indicate the name of a file to process. The program will store the contents of the file so that we can display the original line in which each word appears
- A good way to start the design of a program is to list the program's operations. Knowing what operations we need to provide can then help us see what data structures we'll need and how we might implement those actions
- 10.6.2. TextQuery Class
- Our class will have two data members: a vector to hold the input file and a map to associate each input word with the set of line numbers on which it appears
- For the reasons described on page 80, this class definition uses fully qualified std:: names for all references to library entities
- Our class will have two data members: a vector to hold the input file and a map to associate each input word with the set of line numbers on which it appears
- 10.6.3. Using the TextQuery Class
- 10.6.4. Writing the Member Functions