C++ Primer(第五版)|练习题答案与解析(第十一章:关联容器)
本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++ Primer
练习题11.1
描述map和vector的不同。
- vector是顺序容器,只能存放单一类型的数据。
- map是关联的容器,存放一对key-value,值表示与索引相关联的数据,这两个数据可以是不同类型。(P374)
练习题11.2
分别给出最适合使用list、vector、deque、map以及set的例子。
-
list ,适用于在中间进行操作的情况。
-
vector, 构建动态数组(不确定大小)。
-
deque,适用于只需要在头尾进行操作的情况。
-
map , 字典(Key-Value)对应。
-
set , 集合。
练习题11.3
编写你自己的单词计数程序。
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <cctype>
using namespace std;
int main()
{
map<string, size_t> word_count;
string word;
while (cin >> word) {
++ word_count[word];
}
for (const auto& w : word_count) {
cout << w.first << " occurs " << w.second << ((w.second > 1) ? " times" : " time.") << endl;
}
return 0;
}
测试:
the C++ is very very interesting
^Z
C++ occurs 1 time.
interesting occurs 1 time.
is occurs 1 time.
the occurs 1 time.
very occurs 2 times
练习题11.4
扩展你的程序,忽略大小写和标点。例如,“example.”、"example,"和"Example"应该递增相同的计数器。
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <cctype>
//using namespace std;
//如果直接使用std,remove_if就编译不过,原因未知
using std::remove_if;
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::map;
int main()
{
map<string, size_t> word_count;
string word;
while (cin >> word) {
for (auto& ch : word) ch = tolower(ch);
word.erase(remove_if(word.begin(), word.end(), ispunct), word.end());
++ word_count[word];
}
for (const auto& w : word_count) {
cout << w.first << " occurs " << w.second << ((w.second > 1) ? " times" : " time.") << endl;
}
return 0;
}
测试:
the C++ is Very very interesting and Interesting.
^Z
and occurs 1 time.
c occurs 1 time.
interesting occurs 2 times
is occurs 1 time.
the occurs 1 time.
very occurs 2 times
练习题11.5
解释map和set的区别。你如何选择使用哪个?
P376
map必须指明关键字类型和值类型
set只需指明关键字类型,没有值。
看情况使用。
练习题11.6
解释set和list的区别。你如何选择使用哪个?
set是关联容器,进行查找、修改操作效率高。
list是顺序容器,插入删除等操作效率低。
练习题11.7
定义一个map,关键字是家庭的姓,值是一个vector,保存家中孩子们的名。编写代码,实现添加新的家庭以及向已有家庭中添加新的孩子。
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
map<string, vector<string>> Families;
string family_name;
vector<string> child_name;
string name;
int if_continue = 1;
while (if_continue) {
cout << "Please input the family name: ";
cin >> family_name;
cout << "Please input the child name: ";
cin >> name;
Families[family_name].push_back(name);
cout << "Continue?(1/0)";
cin >> if_continue;
}
for (const auto& f : Families) {
cout << f.first << "\t\t";
for (const auto n : f.second) {
cout << n << "\t";
}
cout << endl;
}
cout << endl;
return 0;
}
测试:
Please input the family name: Bod
Please input the child name: Jack
Continue?(1/0)1
Please input the family name: ROse
Please input the child name: Zer
Continue?(1/0)0
Bod Jack
ROse Zer
练习题11.8
编写一个程序,在一个vector而不是一个set中保存不重复的单词。使用set的优点是什么?
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<string> exclude = { "test", "the", "C++" };
for (string word; cout << "Enter plz:\n", cin >> word; )
{
auto is_excluded = binary_search(exclude.cbegin(), exclude.cend(), word);
auto reply = is_excluded ? "excluded" : "not excluded";
cout << reply << endl;
}
return 0;
}
测试:
Enter plz:
train
not excluded
Enter plz:
test
excluded
Enter plz:
go
not excluded
Enter plz:
^Z
set具有更快的查找速度。
练习题11.9
定义一个map,将单词与一个行号的list关联,list中保存的是单词所出现的行号。
定义:map<string, list<size_t>> m;
满足key-value。
练习题11.10
可以定义一个vector::iterator到int的map吗?list::iterator到int的map呢?对于这两种情况,如果不能,解释为什么。
map<vector<int>::iterator, int> m;
map<list<int>::iterator, int> l;
可以被声明,可以编译通过,但不能实际使用,因为iterator不支持比较操作。(P378).要排序就得支持比较。
练习题11.11
不使用decltype重新定义bookstore。
#include <iostream>
#include <string>
#include <set>
#include "Sales_data.h"
using namespace std;
bool compareIsbn(const Sales_data& lhs, const Sales_data& rhs)
{
return lhs.isbn() > rhs.isbn();
}
int main()
{
using compare = bool(*)(const Sales_data&, const Sales_data&);
multimap<Sales_data, compare> bookstore(compareIsbn);
return 0;
}
练习题11.12
编写程序,读入string和int的序列,将每个string和int存入一个pair中,pair保存在一个vector中。
练习题11.13
在上一题的程序中,至少有三种创建pair的方法。编写此程序的三个版本,分别采用不同的方法创建pair。解释你认为哪种形式最易于编写和理解,为什么?
#include <vector>
#include <utility>
#include <string>
#include <iostream>
using namespace std;
int main()
{
vector<pair<string, int>> vec;
vector<pair<string, int>> vec1;
vector<pair<string, int>> vec2;
vector<pair<string, int>> vec3;
string str;
int i;
while (cin >> str >> i)
vec.push_back(pair<string, int>(str, i));
vec1.push_back(make_pair(str, i));
vec2.push_back({ str, i });
vec3.emplace_back(str, i); //最简单
cout<<"vec :";
for (const auto &p : vec)
cout << p.first << ":" << p.second << endl;
cout<<"vec1:";
for (const auto &p : vec1)
cout << p.first << ":" << p.second << endl;
cout<<"vec2:";
for (const auto &p : vec2)
cout << p.first << ":" << p.second << endl;
cout<<"vec3:";
for (const auto &p : vec3)
cout << p.first << ":" << p.second << endl;
}
测试:
Bob 16
^Z
vec :Bob:16
vec1:Bob:16
vec2:Bob:16
vec3:Bob:16
练习题11.14
扩展11.2.1节练习中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名和生日。
#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;
class Families
{
public:
using Child = pair<string, string>;
using Children = vector<Child>;
using Data = map<string, Children>;
void add(string const& last_name, string const& first_name, string birthday)
{
auto child = make_pair(first_name, birthday);
_data[last_name].push_back(child);
}
void print() const
{
for (auto const& pair : _data)
{
cout << pair.first << ":\n" ;
for (auto const& child : pair.second)
cout << child.first << " " << child.second << endl;
cout << endl;
}
}
private:
Data _data;
};
int main()
{
Families families;
auto msg = "Please enter last name, first name and birthday:\n";
for (string l, f, b; cout << msg, cin >> l >> f >> b; families.add(l, f, b));
families.print();
return 0;
}
测试:
Please enter last name, first name and birthday:
Bob Jack 95.02.08
Please enter last name, first name and birthday:
Bob Rose 02.08.25
Please enter last name, first name and birthday:
Chen Druk 99.05.20
Please enter last name, first name and birthday:
^Z
Bob:
Jack 95.02.08
Rose 02.08.25
Chen:
Druk 99.05.20
练习题11.15
对一个int到vector的map,其mapped_type、key_type和value_type分别是什么?
P381表11.3。对于map<int, vector> 类型,mapped_type是vector类型。key_type是int类型。value_type是pair<int, vector>类型
练习题11.16
使用一个map迭代器编写一个表达式,将一个值赋予一个元素。
#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main()
{
map<string, size_t> word;
word["test"] = 10;
map<string, size_t>::iterator map_it = word.begin();
for(const auto &w:word)
cout<<w.first<<" "<<w.second<<endl;
//map_it->first = "train";//错误用法 const string不能修改
map_it->second = 5;
for(const auto &w:word)
cout<<w.first<<" "<<w.second<<endl;
return 0;
}
测试:
test 10
test 5
练习题11.17
假定c是一个string的multiset,v是一个string的vector,解释下面的调用。指出每个调用是否合法:
(a)copy(v.begin(), v.end(), inserter(c, c.end()));
将v中的元素拷贝到c中,使用合法,可以使用inserter将关联容器当作一个目的位置。
(b)copy(v.begin(), v.end(), back_inserter(c));
将v中的元素拷贝到c中,但不合法,因为multiset没有push_back方法,不能调用back_inserter
(c)copy(c.begin(), c.end(), inserter(v, v.end()))
;将c中的元素拷贝到v中,使用合法,vector可以使用inserter。
(d)copy(c.begin(), c.end(), back_inserter(v));
将c中的元素拷贝到v中,使用合法,vector有push_back方法,可以使用back_inserte。
练习题11.18
写出382页循环中map_it的类型,不要使用auto或decltype。
为:map<string, size_t>::const_iterator map_it = word.cbegin();
练习题11.19
定义一个变量,通过11.2.2节中的名为bookstore的multiset调用begin()来初始化这个变量。写出变量的类型,不要使用auto或decltype。
using compareType = bool (*)(const Sales_data &lhs, const Sales_data &rhs);
std::multiset<Sales_data, compareType> bookstore(compareIsbn);
std::multiset<Sales_data, compareType>::iterator c_it = bookstore.begin();
练习题11.20
重写11.1节练习的单词计数程序,使用insert代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<string, size_t> counts;
for(string word; cin >> word;)
{
auto result = counts.insert({ word, 1 });
if(!result.second)
++result.first->second;
}
for(auto const& count : counts)
cout << count.first << " " << count.second << ((count.second > 1) ? " times\n" : " time\n");
}
测试:
test
train
do
test
train
train
^Z
do 1 time
test 2 times
train 3 times
练习题11.21
假定word_count是一个string到size_t的map,word是一个string,解释下面循环的作用。
while (cin >> word)
++word_count.insert({ word, 0 }).first->second;
向word_count中插入元素,如果word已存在,则解引用first迭代器,取值的部分++。如果word不存在,则插入(word,0),载解引用first迭代器,取值的部分++。和上题计数类似。
练习题11.22
给定一个map<string, vector>, 对此容器的插入一个元素的insert版本,写出其参数类型和返回类型。
参数类型:pair<string, vector<int>>
返回值类型:pair<map<string,vector<int>>::iterator, bool>
练习题11.23
11.2.1节练习中的map以孩子的姓为关键字,保存他们的名的vector,用multimap重写此map。
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
multimap<string, string> families;
for (string lname, cname; cin >> cname >> lname; families.emplace(lname, cname));
for (auto const& family : families)
cout << family.second << " " << family.first << endl;
}
测试:
Jack Rose
Jack Chen
Bob Bush
^Z
Bob Bush
Jack Chen
Jack Rose
练习题11.24
下面的程序完成什么功能?
map<int, int> m;
m[0] = 1;
在m中添加一个关键字0,将值“1”赋给它。
练习题11.25
对比下面程序与上一题程序
vector<int> v;
v[0] = 1;
将值“1” 放在vector v中的第一个位置(索引为0)。
练习题11.26
可以用什么类型来对一个map进行下标操作?下标运算符返回的类型是什么?请给出一个具体例子 – 即,定义一个map,然后写出一个可以用来对map进行下标操作的类型以及下标运算符会返回的类型。
#include <iostream>
#include <map>
#include <string>
#include <typeinfo>
using namespace std;
int main()
{
map<int, string> m = { { 1,"test" },{ 2,"train" } };
using KeyType = map<int, string>::key_type;
cout << "map类型下标: " << typeid(KeyType).name() << endl;
cout << "从map下标操作符返回: " << typeid(decltype(m[1])).name() << endl;
return 0;
}
测试:
map类型下标: i
从map下标操作符返回: Ss
练习题11.27
对于什么问题你会使用count来解决?什么时候你又会选择find呢?
P389。
- 使用find:只关心一个特定的元素是否在容器中。
- 使用count:在允许重复关键字的容器中,如果要对某个特定的元素进行计数。
练习题11.28
对于一个string到int的vector的map,定义并初始化一个变量来保存在其上调用find所返回的结果。
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
map<string, vector<int>> vmap { { "test",{ 1,2,3 } },{ "train",{ 4,5,6 } }};
map<string, vector<int>>::iterator iter;
iter = vmap.find("test");
cout<<((iter != vmap.end() )? "find" : "not find");
return 0;
}
测试:find
练习题11.29
如果给定的关键字不在容器中,upper_bound、lower_bound和equal_range分别会返回什么?
P390。
如果给定的关键字不在容器中, 则upper_bound和lower_bound都返回关键字的第一个安全插入点 – 不影响容器中元素顺序的插入位置。equal_range返回的两个迭代器都指向关键字可以插入的位置。
练习题11.30
对于本节最后一个程序中的输出表达式,解释运算对象pos.first->second的含义
pos.first->second意为给定关键字返回的第一个迭代器的value值。P391。
练习题11.31
编写程序,定义一个作者及其作品的multimap。使用find在multimap中查找一个元素并用erase删除它。确保你的程序在元素不在map中时也能正常运行。
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
multimap<string, string> auth_map = {
{"Andrew", "AI"},
{"Joe", "science"},
{"Andrew", "ML"},
{"Angle", "design"},
{"Andrew", "NN"}
};
string auth_name = "Andrew";
string work = "NN";
auto iter = auth_map.find(auth_name);
auto count = auth_map.count(auth_name);
while (count) {
if (iter->second == work) {
auth_map.erase(iter);
break;
}
else {
++ iter;
-- count ;
}
}
for (auto i : auth_map) {
cout << i.first << " " << i.second << endl;
}
return 0;
}
测试:
Andrew AI
Andrew ML
Angle design
Joe science
练习题11.32
使用上一题定义的multimap编写一个程序,按字典序打印作者列表和他们的作品。
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
int main()
{
multimap<string, string> auth_map = {
{"Andrew", "AI"},
{"Joe", "science"},
{"Andrew", "ML"},
{"Angle", "design"},
{"Andrew", "NN"}
};
map <string, multiset<string>> order_auth;
for (auto& auth : auth_map) {
order_auth[auth.first].insert(auth.second);
}
for (const auto &author : order_auth) {
cout << author.first << ": ";
for (const auto &work : author.second)
cout << work << " ";
cout << endl;
}
return 0;
}
测试:
Andrew: AI ML NN
Angle: design
Joe: science
练习题11.33
实现你自己版本的单词转换程序。
#include <map>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace std;
map<string, string> buildMap(ifstream& map_file)
{
map<string, string> trans_map;
string key, value;
while (map_file >> key && getline(map_file, value)) {
if (value.size() > 1) {
trans_map[key] = value.substr(1); // 去掉value前的空格
}
else {
throw runtime_error("no rules for " + key);
}
}
return trans_map;
}
const string& transform(const string& s, const map<string, string>& m)
{
auto map_iter = m.find(s);
if (map_iter != m.end()) {
return map_iter->second;
}
// if find the word in map, return the phrase. else return the origin word.
else {
return s;
}
}
void wordTransform(ifstream& map_file, ifstream& input)
{
auto trans_map = buildMap(map_file);
string text;
while (getline(input, text)) {
istringstream stream(text);
string word;
bool first_word = true;
while (stream >> word) {
if (first_word) {
first_word = false;
}
else {
cout << " ";
}
cout << transform(word, trans_map);
}
cout << endl;
}
}
int main()
{
string transform_file = "transform_file.txt";
string input_file = "input_file.txt";
ifstream trans(transform_file);
ifstream input (input_file);
if (trans && input) {
wordTransform (trans, input);
}
else {
cout << "open file error!" << endl;
}
return 0;
}
测试:
whrere are you
why dont you send me a picture
okay? thanks! later
练习题11.34
如果你将transform函数中的find替换为下标运算符,会发生什么情况
若使用下标运算符,如果转换map中要查找的key不存在,则会直接插入一条新的数据,value为空。
练习题11.35
在buildMap中,如果进行如下改写,会有什么效果?
trans_map[key] = value.substr(1);
改为trans_map.insert({key, value.substr(1)})
如果转换文件中有重复key,若使用下标进行插入,则value是最后文件中key对应的最后一个短语。而如果使用insert,则key对应的是第一个短语。
练习题11.36
我们的程序并没有检查输入文件的合法性,特别是,它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,再与你的程序进行比较。
报错:
terminate called after throwing an instance of 'std::runtime_error'
what(): no rules for brb
练习题11.37
一个无序容器与其有序版本相比有何优势?有序版本有何优势?
P394。
有序容器的特点:
- 以顺序遍历元素,顺序可规定,默认情况下使用"<"进行排序。
- 主要使用排序vector,二叉搜索树实现。
- 查找元素操作的时间复杂度为O(logn)。
- 插入、移动元素操作的时间复杂度更低,最大为O(logn)。
无序容器的特点:
- 遍历元素没有特定的顺序。
- 如果hash策略良好,则搜索、插入、移动操作的时间复杂度都是常数。
- 主要使用哈希实现。
练习题11.38
用unordered_map重写单词计数程序和单词转换程序。
#include <unordered_map>
#include <set>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
void wordCounting()
{
unordered_map<string, size_t> word_count;
for (string word; cin >> word; ++word_count[word]);
for (const auto &w : word_count)
cout << w.first << " occurs " << w.second << (w.second > 1 ? "times" : "time") << endl;
}
void wordTransformation()
{
ifstream ifs_map("transform_file.txt"), ifs_content("input_file.txt");
if (!ifs_map || !ifs_content) {
cerr << "can't find the documents." << endl;
return;
}
unordered_map<string, string> trans_map;
for (string key, value; ifs_map >> key && getline(ifs_map, value); )
if (value.size() > 1) trans_map[key] = value.substr(1).substr(0, value.find_last_not_of(' '));
for (string text, word; getline(ifs_content, text); cout << endl)
for (istringstream iss(text); iss >> word; ) {
auto map_it = trans_map.find(word);
cout << (map_it == trans_map.cend() ? word : map_it->second) << " ";
}
}
int main()
{
//wordCounting();
wordTransformation();
}