《C++ Primer第五版》读书笔记(9)--ASSOCIATIVE CONTAINERS

来源:互联网 发布:ori什么软件 编辑:程序博客网 时间:2024/04/30 19:33
Elements in an associative container are stored and retrieved by a key. In contrast, elements in a sequential container are stored and accessed sequentially by their position in the container.
The two primary associative-container types are map and set.The library provides eight associative containers, listed in Table 11.1.
 
11.1 USING AN ASSOCIATIVE CONTAINER
The map type is often referred to as an associative array.
A set is most useful when we simply want to know whether a value is present. 


Using a map:
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
        // count the number of times each word occurs in the input
        map<string, size_t> word_count; // empty map from string to size_t
        string word;
        while (cin >> word)
           ++word_count[word];  // fetch and increment the counter for word
        for (const auto &w : word_count) // for each element in the map
                      // print the results
                        cout <<  w.first << " occurs " << w.second<< ((w.second > 1) ? " times" : " time") << endl;
        return 0;
}
Like the sequential containers, the associative containers are templates. To define a map, we must specify both the key and value types.


Using a set

A logical extension to our program is to ignore common words like “the,” “and,” “or,” and so on.
// count the number of times each word occurs in the input
map<string, size_t> word_count; // empty map from string to size_t
set<string> exclude = {"The", "But", "And", "Or", "An", "A","the", "but", "and", "or", "an", "a"};
string word;
while (cin >> word)
// count only words that are not in exclude
if (exclude.find(word) == exclude.end())

    ++word_count[word];  // fetch and increment the counter for word


11.2 OVERVIEW OF THE ASSOCIATIVE CONTAINERS


The associative containers do not support the sequential-container position-specific operations, such as push_front or back. Because the elements are stored based on their keys, these operations would be meaningless for the associative containers. Moreover, the associative containers do not support the constructors or insert operations that take an element value and a count.
In addition to the operations they share with the sequential containers, the associative containers provide some operations (Table 11.7) and type aliases (Table 11.3) that the sequential containers do not. In addition, the unordered containers provide operations for tuning their hash performance, which we’ll cover in §11.4.
The associative container iterators are bidirectional.


11.2.1 DEFINING AN ASSOCIATIVE CONTAINER

Each of the associative containers defines a default constructor, which creates an empty container of the specified type. We can also initialize an associative container as a copy of another container of the same type or from a range of values, so long as those values can be converted to the type of the container. Under the new standard, we can also list initialize the elements:


map<string,size_t> word_count;  // empty

// list initialization
set<string> exclude = {"the", "but", "and", "or", "an", "a","The", "But", "And", "Or", "An","A"};
// three elements; authors maps last name to first
map<string, string> authors = { {"Joyce", "James"},{"Austen", "Jane"},{"Dickens", "Charles"} };


Initializing a multimap or multiset

The keys in a map or a set must be unique; there can be only one element with a given key. The multimap and multiset containers have no such restriction; there can be several elements with the same key.


11.2.2 REQUIREMENTS ON KEY TYPE
For the ordered containers—map, multimap, set, and multiset—the key type must define a way to compare the elements. By default, the library uses the <operator for the key type to compare the keys. Callable objects passed to a sort algorithm must meet the same requirements as do the keys in an associative container.

we can also supply our own operation to use in place of the <operator on keys.The specified operation must define a strict weak orderingover the key type. We can think of a strict weak ordering as “less than,” although our function might use a more complicated procedure. However we define it, the comparison function must have the following properties:


Using a Comparison Function for the Key Type:
// bookstore can have several transactions with the same ISBN
// elements in bookstore will be in ISBN order
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);

we use decltype to specify the type of our operation, remembering that when we usedecltype to form a function pointer, we must add a * to indicate that we’re using a pointer to the given function type.


11.2.3 THE PAIR TYPE

Pair is a template from which we generate specific types.


pair<string,string> anon;  // holds two strings
pair<string, size_t> word_count; // holds a string and an size_t

pair<string, vector<int>> line;  // holds string and vector<int>


Elements in a map are pairs.
The library defines only a limited number of operations on pairs, which are listed in Table 11.2
 
Under the new standard we can list initialize the return value:
pair<string, int> process(vector<string> &v)
{
// process v
if (!v.empty())
   return {v.back(), v.back().size()}; // list initialize
else
   return pair<string, int>(); // explicitly constructed return value

}


11.3 OPERATIONS ON ASSOCIATIVE CONTAINERS
In addition to the types listed in Table 9.2(p. 330), the associative containers define the types listed in Table 11.3.
 
set<string>::value_type v1;  // v1 is a string
set<string>::key_type v2;  // v2 is a string
map<string, int>::value_type v3; // v3 is a pair<const string, int>
map<string, int>::key_type v4;  // v4 is a string

map<string, int>::mapped_type v5; // v5 is an int


11.3.1 ASSOCIATIVE CONTAINER ITERATORS
When we dereference an iterator, we get a reference to a value of the container’s value_type. In the case of map, the value_type is a pair in which first holds the const key and second holds the value:
// get an iterator to an element in word_count
auto map_it = word_count.begin();
// *map_it is a reference to a pair<const string, size_t> object
cout << map_it->first;  // prints the key for this element
cout << " " << map_it->second;  // prints the value of the element
map_it->first = "new key";  // error: key is const
++map_it->second;  // ok: we can change the value through an iterator 


It is essential to remember that the value_type of a map is a pair and that we can change the value but not the key member of that pair.


Iterators for sets Are const

Although the set types define both the iterator and const_iterator types, both types of iterators give us read-only access to the elements in the set. Just as we cannot change the key part of a map element, the keys in a set are also const.


Iterating across an Associative Container
// get an iterator positioned on the first element
auto map_it = word_count.cbegin();
// compare the current iterator to the off-the-end iterator
while (map_it != word_count.cend()) {
// dereference the iterator to print the element key--value pairs
cout << map_it->first << " occurs "<< map_it->second << " times" << endl;
++map_it;  // increment the iterator to denote the next element
}
When we use an iterator to traverse a map, multimap, set, or multiset, the iterators yield elements in ascending key order.


Associative Containers and Algorithms

In general, we do not use the generic algorithms (Chapter 10) with the associative containers. The fact that the keys are const means that we cannot pass associative container iterators to algorithms that write to or reorder container elements. Associative containers can be used with the algorithms that read elements. However, many of these algorithms search the sequence. Because elements in an associative container can be found (quickly) by their key, it is almost always a bad idea to use a generic search algorithm. For example, as we’ll see in § 11.3.5(p. 436), the associative containers define a member named find, which directly fetches the element with a given key. We could use the generic find algorithm to look for an element, but that algorithm does a sequential search. It is much faster to use the find member defined by the container than to call the generic version.

In practice, if we do so at all, we use an associative container with the algorithms either as the source sequence or as a destination. For example, we might use the generic copy algorithm to copy the elements from an associative container into another sequence. Similarly, we can call inserter to bind an insert iterator to an associative container. Using inserter, we can use the associative container as a destination for another algorithm.


11.3.2 ADDING ELEMENTS

The insert members (Table 11.4(overleaf)) add one element or a range of elements. Because map and set(and the corresponding unordered types) contain unique keys, inserting an element that is already present has no effect:


vector<int>ivec = {2,4,6,8,2,4,6,8};  // ivec has eight elements
set<int> set2;  // empty set
set2.insert(ivec.cbegin(), ivec.cend()); // set2 has four elements
set2.insert({1,3,5,7,1,3,5,7});  // set2 now has eight elements
 
Adding Elements to a map:
// four ways to add word to word_count
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));

word_count.insert(map<string, size_t>::value_type(word, 1));


Testing the Return from insert:


For the containers that have unique keys, the versions of insert and emplace that add a single element return a pair that lets us know whether the insertion happened. The first member of the pair is an iterator to the element with the given key; the second is a bool indicating whether that element was inserted, or was already there. If the key is already in the container, then insert does nothing, and the bool portion of the return value is false. If the key isn’t present, then the element is inserted and the bool is true.
// more verbose way to count number of times each word occurs in the input
map<string, size_t> word_count; // empty map from string to size_t
string word;
while (cin >> word) {
   // inserts an element with key equal to word and value 1;
   // if word is already in word_count, insert does nothing
   auto ret = word_count.insert({word, 1});
   if (!ret.second)  // word was already in word_count
       ++ret.first->second; // increment the counter

}


Adding Elements to multiset or multimap


Because keys in a multi container need not be unique, insert on these types always inserts an element:


multimap  <string,string> authors;
// adds the first element with the key Barth, John
authors.insert({"Barth, John", "Sot-Weed Factor"});
// ok: adds the second element with the key Barth, John
authors.insert({"Barth, John", "Lost in the Funhouse"});

For the containers that allow multiple keys, the insertoperation that takes a single element returns an iterator to the new element. There is no need to return a bool, because insert always adds a new element in these types.


11.3.3 ERASING ELEMENTS
Table 11.5. Removing Elements from an Associative Container
 
11.3.4 SUBSCRIPTING A MAP
The map and unordered_map containers provide the subscript operator and a corresponding at function. The set types do not support subscripting because there is no “value” associated with a key in a set. The elements are themselves keys, so the operation of “fetching the value associated with a key” is meaningless. We cannot subscript a multimap or an unordered_multimap because there may be more than one value associated with a given key.

 

map<string, size_t> word_count; // empty map

// insert a value-initialized element with key Anna; then assign 1 to its value
word_count["Anna"] = 1;
Because the subscript operator might insert an element, we may use subscript only on a map that is not const.
Ordinarily, the type returned by dereferencing an iterator and the type returned by the subscript operator are the same. Not so for maps: when we subscript a map, we get a mapped_type object; when we dereference a map iterator, we get a value_type object
In common with other subscripts, the map subscript operator returns an lvalue. Because the return is an lvalue, we can read or write the element:

The fact that the subscript operator adds an element if it is not already in the map allows us to write surprisingly succinct programs such as the loop inside our wordcounting program. On the other hand, sometimes we only want to know whether an element is present and do notwant to add the element if it is not. In such cases, we must not use the subscript operator.


11.3.5 ACCESSING ELEMENTS
The associative containers provide various ways to find a given element, which are described in Table 11.7.
 

Sometimes,we want to know if an element with a given key is present without changing the map. We cannot use the subscript operator to determine whether an element is present, because the subscript operator inserts a new element if the key is not already there. In such cases, we should use find:


if(word_count.find("foobar") == word_count.end()) 

   cout << "foobar is not in the map" << endl;


Finding Elements in a multimap or multiset:


string search_item("Alain de Botton"); // author we'll look for
auto entries = authors.count(search_item); // number of elements
auto iter = authors.find(search_item); // first entry for this author
// loop through the number of entries there are for this author
while(entries) {
   cout << iter->second << endl; // print each title
   ++iter;  // advance to the next title
   --entries;  // keep track of how many we've printed

}


A Different, Iterator-Oriented Solution
// definitions of authors and search_item as above
// beg and end denote the range of elements for this author
for (auto beg = authors.lower_bound(search_item),end = authors.upper_bound(search_item);beg != end; ++beg)

  cout << beg->second << endl; // print each title


The equal_range Function
This function takes a key and returns a pairof iterators. If the key 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.
// definitions of authors and search_item as above
// pos holds iterators that denote the range of elements for this key
for (auto pos = authors.equal_range(search_item);
   pos.first != pos.second; ++pos.first)

cout << pos.first->second << endl; // print each title


11.4 THE UNORDERED CONTAINERS
The new standard defines four unordered associative containers. Rather than using a comparison operation to organize their elements, these containers use a hash function and the key type’s ==operator. An unordered container is most useful when we have a key type for which there is no obvious ordering relationship among the elements.

Although hashing gives better average case performance in principle, achieving good results in practice often requires a fair bit of performance testing and tweaking. As a result, it is usually easier (and often yields better performance) to use an ordered container. Use an unordered container if the key type is inherently unordered or if performance testing reveals problems that hashing might solve.


Aside from operations that manage the hashing, the unordered containers provide the same operations (find, insert, and so on) as the ordered containers. That means that the operations we’ve used on map and set apply to unordered_map and unordered_setas well. Similarly for the unordered versions of the containers that allow multiple keys.


As a result, we can usually use an unordered container in place of the corresponding ordered container, and vice versa. However, because the elements are not stored in order, the output of a program that uses an unordered container will (ordinarily) differ from the same program using an ordered container.

The unordered containers are organized as a collection of buckets, each of which holds zero or more elements. These containers use a hash function to map elements to buckets. To access an element, the container first computes the element’s hash code, which tells which bucket to search. The container puts all of its elements with a given hash value into the same bucket. If the container allows multiple elements with a given key, all the elements with the same key will be in the same bucket. As a result, the performance of an unordered container depends on the quality of its hash function and on the number and size of its buckets.


The unordered containers provide a set of functions, listed in Table 11.8, that let us manage the buckets. These members let us inquire about the state of the container and force the container to reorganize itself as needed.
Table11.8. Unordered Container Management Operations
 
By default, the unordered containers use the ==operator on the key type to compare elements.
They also use an object of type hash<key_type> to generate the hash code for each element. The library supplies versions of the hash template for the builtin types, including pointers.It also defines hash for some of the library types, including strings and the smart pointer types that we will describe in Chapter 12. Thus, we can directly define unordered containers whose key is one of the built-in types (including pointer types), or a string, or a smart pointer.
0 0
原创粉丝点击