Team LiB
Previous Section Next Section

10.4. The set Type

10.4. set 类型

A map is a collection of a keyvalue pairs, such as an address and phone number keyed to an individual's name. In contrast, a set is simply a collection of keys. For example, a business might define a set named bad_checks, to hold the names of individuals who have issued bad checks to the company. A set is most useful when we simply want to know whether a value is present. Before accepting a check, for example, that business would query bad_checks to see whether the customer's name was present.

map 容器是键-值对的集合,好比以人名为键的地址和电话号码。相反地,set 容器只是单纯的键的集合。例如,某公司可能定义了一个名为 bad_checksset 容器,用于记录曾经给本公司发空头支票的客户。当只想知道一个值是否存在时,使用 set 容器是最适合的。例如,在接收一张支票前,该公司可能想查询 bad_checks 对象,看看该客户的名字是否存在。

With two exceptions, set supports the same operations as map:

除了两种例外情况,set 容器支持大部分的 map 操作,包括下面几种:

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. These differences reflect the fact that set holds only keys; there is no value associated with the key. As with map, the keys of a set must be unique and may not be changed.

两种例外包括:set 不支持下标操作符,而且没有定义 mapped_type 类型。在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是 set 中存储的元素类型。这一差别也体现了 set 存储的元素仅仅是键,而没有所关联的值。与 map 一样,set 容器存储的键也必须唯一,而且不能修改。

Exercises Section 10.4

Exercise 10.21:

Explain the difference between a map and a set. When might you use one or the other?

解释 mapset 容器的差别,以及它们各自适用的情况。

Exercise 10.22:

Explain the difference between a set and a list. When might you use one or the other?

解释 setlist 容器的差别,以及它们各自适用的情况。


10.4.1. Defining and Using sets

10.4.1. set 容器的定义和使用

To use a set, we must include the set header. The operations on sets are essentially identical to those on maps.

为了使用 set 容器,必须包含 set 头文件。set 支持的操作基本上与 map 提供的相同。

As with map, there can be only one element with a given key in a set. When we initialize a set from a range of elements or insert a range of elements, only one element with a given key is actually added:

map 容器一样,set 容器的每个键都只能对应一个元素。以一段范围的元素初始化 set 对象,或在 set 对象中插入一组元素时,对于每个键,事实上都只添加了一个元素:

     // define a vector with 20 elements, holding two copies of each number from 0 to 9
     vector<int> ivec;
     for (vector<int>::size_type i = 0; i != 10; ++i) {
         ivec.push_back(i);
         ivec.push_back(i); // duplicate copies of each number
     }
     // iset holds unique elements from ivec
     set<int> iset(ivec.begin(), ivec.end());
     cout << ivec.size() << endl;      // prints 20
     cout << iset.size() << endl;      // prints 10

We first create a vector of ints named ivec that has 20 elements: two copies of each of the integers from 0 through 9 inclusive. We then use all the elements from ivec to initialize a set of ints. That set has only ten elements: one for each distinct element in ivec.

首先创建了一个名为 ivecintvector 容器,存储 20 个元素:0-9(包括 9)中每个整数都出现了两次。然后用 ivec 中所有的元素初始化一个 int 型的 set 容器。则这个 set 容器仅有 10 个元素:ivec 中不相同的各个元素。

Adding Elements to a set
set 中添加元素

We can add elements to a set by using the insert operation:

可使用 insert 操作在 set 中添加元素:

     set<string> set1;         // empty set
     set1.insert("the");       // set1 now has one element
     set1.insert("and");       // set1 now has two elements

Alternatively, we can insert a range of elements by providing a pair of iterators to insert. This version of insert works similarly to the constructor that takes an iterator paironly one element with a given key is inserted:

另一种用法是,调用 insert 函数时,提供一对迭代器实参,插入其标记范围内所有的元素。该版本的 insert 函数类似于形参为一对迭代器的构造函数——对于一个键,仅插入一个元素:

     set<int>    iset2; //    empty set
     iset2.insert(ivec.begin(), ivec.end());     // iset2 has 10 elements

Like the map operations, the version of insert that takes a key returns a pair containing an iterator to the element with this key and a bool indicating whether the element was added. The one that takes an iterator pair returns void.

map 容器的操作一样,带有一个键参数的 insert 版本返回 pair 类型对象,包含一个迭代器和一个 bool 值,迭代器指向拥有该键的元素,而 bool 值表明是否添加了元素。使用迭代器对的 insert 版本返回 void 类型。

Fetching an Element from a set
set 中获取元素

There is no subscript operator on sets. 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. Of course, for set that value can be only one (if the element is present) or zero (if it is not):

set 容器不提供下标操作符。为了通过键从 set 中获取元素,可使用 find 运算。如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数。当然,对于 set 容器,count 的返回值只能是 1(该元素存在)或 0(该元素不存在):

     iset.find(1)     // returns iterator that refers to the element with key == 1
     iset.find(11)    // returns iterator == iset.end()

     iset.count(1)    // returns 1
     iset.count(11)   // returns 0

Just as we cannot change the key part of a map element, the keys in a set are also const. If we have an iterator to an element of the set, all we can do is read it; we cannot write through it:

正如不能修改 map 中元素的键部分一样,set 中的键也为 const。在获得指向 set 中某元素的迭代器后,只能对其做读操作,而不能做写操作:

     // set_it refers to the element with key == 1
     set<int>::iterator set_it = iset.find(1);
     *set_it = 11;               // error: keys in a set are read-only
     cout << *set_it << endl;    // ok: can read the key

10.4.2. Building a Word-Exclusion Set

10.4.2. 创建“单词排除”集

On page 369 we removed a given word from our word_count map. We might want to extend this approach to remove all the words in a specified file. That is, our word-count program should count only words that are not in a set of excluded words. Using set and map, this program is fairly straightforward:

第 10.3.7 节的程序从 map 对象 word_count 中删除一个指定的单词。可将这个操作扩展为删除指定文件中所有的单词(即该文件记录的是排除集)。也即,我们的单词统计程序只对那些不在排除集中的单词进行统计。使用 setmap 容器,可以简单而直接地实现该功能:

     void restricted_wc(ifstream &remove_file,
                        map<string, int> &word_count)
     {
         set<string> excluded; // set to hold words we'll ignore
         string remove_word;
         while (remove_file >> remove_word)
             excluded.insert(remove_word);
         // read input and keep a count for words that aren't in the exclusion set
         string word;
         while (cin >> word)
            // increment counter only if the word is not in excluded
            if (!excluded.count(word))
                 ++word_count[word];
     }

This program is similar to the word-count program on page 363. The difference is that we do not bother to count the common words.

这个程序类似第 10.3.4 节的单词统计程序。其差别在于不需要费力地统计常见的单词。

The function starts by reading the file it was passed. That file contains the list of excluded words, which we store in the set named excluded. When the first while completes, that set contains an entry for each word in the input file.

该函数首先读取传递进来的文件,该文件列出了所有被排除的单词。读入这些单词并存储在一个名为 excludedset 容器中。第一个 while 循环完成捍,该 set 对象包含了输入文件中的所有单词。

The next part of the program looks a lot like our original word-count program. The important difference is that before counting each word, we check whether the word is in the exclusion set. We do this check in the if inside the second while:

接下来的程序类似原来的单词统计程序。关键的区别在于:在统计每个单词之前,先检查该单词是否出现在排除集中。第二个 while 循环里的 if 语句实现了该功能:

     // increment counter only if the word is not in excluded
     if (!excluded.count(word))

The call to count returns one if word is in excluded and zero otherwise. We negate the return from count so that the test succeeds if word is not in excluded. If word is not in excluded, we update its value in the map.

如果该单词出现在排除集 excluded 中,则调用 count 将返回 1,否则返回 0。对 count 的返回值做“非”运算,则当该 word 不在 excluded 中时,条件测试成功,此时修改该单词在 map 中对应的值。

As in the previous version of our word count program, we rely on the fact that subscripting a map inserts an element if the key is not already in the map. Hence, the effect of

与单词统计程序原来的版本一样,需要使用下标操作符的性质:如果某键尚未在 map 容器中出现,则将该元素插入容器。所以语句

     ++word_count[word];

is to insert word into word_count if it wasn't already there. If the element is inserted, its value is initially set to 0. Regardless of whether the element had to be created, the value is then incremented.

的效果是:如果 word 还没出现过,则将它插入到 word_count 中,并在插入元素后,将它关联的值初始化为 0。然后不管是否插入了新元素,相应元素的值都加 1。

Exercises Section 10.4.2

Exercise 10.23:

Write a program that stores the excluded words in a vector instead of in a set. What are the advantages to using a set?

编写程序将被排除的单词存储在 vector 对象中,而不是存储在 set 对象中。请指出使用 set 的好处。

Exercise 10.24:

Write a program that generates the non-plural version of a word by stripping the 's' off the end of the word. Build an exclusion set to recognize words in which the trailing 's' should not be removed. Two examples of words to place in this set are success, class. Use this exclusion set to write a program that strips plural suffixes from its input but leaves words in the exclusion set unchanged.

编写程序通过删除单词尾部的‘s’生成该单词的非复数版本。同时,建立一个单词排除集,用于识别以‘s’结尾、但这个结尾的‘s’又不能删除的单词。例如,放在该排除集中的单词可能有 successclass。使用这个排除集编写程序,删除输入单词的复数后缀,而如果输入的是排除集中的单词,则保持该单词不变。

Exercise 10.25:

Define a vector of books you'd like to read within the next six months and a set of titles that you've read. Write a program that chooses a next book for you to read from the vector, provided that you have not yet read it. When it returns the selected title to you, it should enter the title in the set of books read. If in fact you end up putting the book aside, provide support for removing the title from the set of books read. At the end of our virtual six months, print the set of books read and those books that were not read.

定义一个 vector 的容器,存储你在未来六个月里要阅读的书,再定义一个 set,用于记录你已经看过的书名。编写程序从 vector 中为你选择一本没有读过而现在要读的书。当它为你返回选中的书名后,应该将该书名放入记录已读书目的 set 中。如果实际上你把这本书放在一边没有看,则本程序应该支持从已读书目的 set 中删除该书的记录。在虚拟的六个月后,输出已读书目和还没有读的书目。


Team LiB
Previous Section Next Section