编译软件为 vs2015。
第十一章
练习 11.1:
描述 map 和 vector 的不同。
map 是关键字—值 对的集合,通过关键字来查找值。
vector 是对象的集合,对象在容器中顺序保存和访问。
练习 11.2:
分别给出最适合使用 list、vector、deque、map 以及 set 的例子。
list:在容器中任意位置插入或删除元素
vector:保存一些重要的关联数据,按顺序查找元素
deque:先进先出
map:字典
set:查找不再序列中的元素
练习 11.3:
编写你自己的单词计数程序。
#include<map>
#include<iostream>
#include<string>
using namespace std;
int main()
{
map<string, size_t> word_count;
string word;
while (cin >> word)
++word_count[word];
for (const auto &c : word_count)
cout <<"( " <<c.first <<" )"<< " occurs " << c.second
<< ((c.second > 1) ? " times" : " time") << endl;
system("pause");
return 0;
}
练习 11.4:
扩展你的程序,忽略大小写和标点。例如 “example.”,“example,” 和 “Example” 应该递增相同的计数器。
#include<string>
#include<iostream>
#include<map>
#include<algorithm>
#include<cctype>
using namespace std;
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;
system("pause");
return 0;
}
练习 11.5:
解释 map 和 set 的区别。你如何选择使用哪个?
解答:
map:元素类型是关键字 — 值对
set:元素类型是关键字
练习 11.6:
解释 set 和 list 的区别。你如何选择使用哪个?
解答:
set 中元素是唯一的和有序的,但 list 都不是。
练习 11.7:
定义一个 map,关键字是家庭的姓,值是一个 vector,保存家中孩子(们)的名。编写代码,实现添加新的家庭以及向已有家庭中添加新的孩子。
解答:
#include<iostream>
#include<string>
#include<vector>
#include<map>
using namespace std;
int main()
{
map<string, vector<string>> fam;
string last_name, childs_name;
cout << "Please input the family name: " << endl;
cin >> last_name;
cout << "Please input new childs' name: " << endl;
while (cin >> childs_name)
{
fam[last_name].push_back(childs_name);
}
for (auto &s : fam)
{
cout << "====================" << endl;
cout << "new family: " << endl;
cout << s.first << endl;
cout << "====================" << endl;
cout << "new childs: " << endl;
for (auto &c : s.second)
cout << c << endl;
}
cout << "====================" << endl;
system("pause");
return 0;
}
练习 11.8:
编写一个程序,在一个 vector 而不是一个 set 中保存不重复的单词。使用 set 的优点是什么?
解答:
#include<iostream>
#include<set>
#include<string>
#include<vector>
using namespace std;
int main()
{
vector<string> vec = { "the", "red", "fox" };
string word;
while (cin >> word)
{
if (find(vec.cbegin(), vec.cend(), word) != vec.cend())
cout << word << " is duplicate word " << endl;
else
vec.push_back(word);
}
for (const auto &c : vec)
cout << c << " ";
cout << endl;
system("pause");
return 0;
}
练习 11.9:
定义一个 map,将单词与一个行号的 list 关联,list 中保存的是单词所出现的行号。
解答:
#include<iostream>
#include<map>
#include<string>
#include<list>
using namespace std;
int main()
{
map<string, list<size_t>> map;
return 0;
}
练习 11.10:
可以定义一个 vector<int>::iterator 到 int 的 map 吗?list<int>::iterator 到 int 的 map 呢?对于两种情况,如果不能,解释为什么。
解答:
两种都可以。
#include<iostream>
#include<map>
#include<string>
#include<vector>
using namespace std;
int main()
{
map<vector<int>::iterator, int> mv;
map<list<int>::iterator, int> ml;
return 0;
}
练习 11.11:
不使用 decltype 重新定义 bookstore。
解答:
#include<iostream>
#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 compareType = bool(*) (const Sales_data &lhs, const Sales_data &rhs);
//typedef bool(*compareType1)(const Sales_data &lhs, const Sales_data &rhs);
multiset<Sales_data, compareType> bookstore(compareIsbn);
return 0;
}
练习 11.12:
编写程序,读入 string 和 int 的序列,将每个 string 和 int 存入一个 pair 中,pair 保存在一个 vector 中。
解答:
#include<iostream>
#include<utility>
#include<vector>
#include<string>
using namespace std;
int main()
{
vector<pair<string, int>>vec;
string str;
int num;
cout << "please input: " << endl;
while (cin >> str >> num)
{
vec.push_back(pair<string, int>(str, num));
}
cout << "====================" << endl;
for (const auto &c : vec)
cout << c.first << " : " << c.second << endl;
system("pause");
return 0;
}
练习 11.13:
在上一题的程序中,至少有三种创建 pair 的方法。编写此程序的三个版本,分别采用不同的方法创建 pair。解析你认为那种形式最易于编写和理解,为什么?
解答:
#include<utility>
#include<vector>
#include<string>
#include<iostream>
using namespace std;
int main()
{
vector<pair<string, int>>vec;
string str;
int num;
while (cin >> str >> num)
{
vec.push_back(make_pair(str, num));
//vec.push_back({str, num})
//vec.emplace_back(str, num); //!!! easiest way.
}
for (const auto &c : vec)
cout << c.first << " : " << endl;
return 0;
}
我个人认为用 make_pair 更易理解和编写程序。
练习 11.14:
扩展你在 11.2.1 节练习(第 378 页)中编写的孩子姓到名的 map,添加一个 pair 的 vector 保存孩子的名和生日。
解答:
#include<iostream>
#include<string>
#include<vector>
#include<map>
using namespace std;
int main()
{
map<string, vector<pair<string, int>>> fam;
string last_name, childs_name;
int bir;
cout << "Please input the family name: " << endl;
cin >> last_name;
cout << "Please input new childs' name ands birthday: " << endl;
while (cin >> childs_name >> bir)
{
fam[last_name].push_back(make_pair(childs_name, bir));
}
for (auto &s : fam)
{
cout << "====================" << endl;
cout << "new family: " << endl;
cout << s.first << endl;
cout << "====================" << endl;
cout << "new childs: " << endl;
for (auto &c : s.second)
cout << c.first << " : " << c.second << endl;
}
cout << "====================" << endl;
system("pause");
return 0;
}
练习 11.15:
对一个 int 到 vector<int> 的 map ,其 mapped_type 、key_type 和 value_type 分别是什么?
解答:
mapped_type:vector<int>
key_type:int
value_type:pair<const int, vector<int>>
练习 11.16:
使用一个 map 迭代器编写一个表达式,将一个值赋予一个元素。
解答:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<int, string> ma;
ma[3] = "Marry";
std::ma<int, string>::iterator it = ma.begin();
it->second = "Mike";
return 0;
}
练习11.17:
假定 c 是一个 string 的 multiset,v 是一个 string 的 vector,解释下面的调用。指出每个调用是否合法:
copy (v.begin(), v.end(), inserter(c, c.end()));
copy (v.begin(), v.end(), back_inserter(c));
copy (c.begin(), c.end(), inserter(v, v.begin()));
copy (c.begin(),c.end(), back_inserter(v));
解答:
1. 合法。将序列 v 插入至 c 至其尾部。
2. 不合法。set 中没有 push_back。
3. 合法。
4. 合法。
练习 11.18:
写出第 382 页循环中 map_it 的类型,不要使用 auto 或 decltype。
解答:
std::map<string, size_t>::const_iterator
练习 11.19:
定义一个变量,通过对 11.2.2 节(第 378 页)中的名为 bookstore 的 multiset 调用 begin() 来初始化这个变量。写出变量的类型,不要使用 auto 或 decltype。
解答:
using compareType = bool (*)(const Sale_data& lhs, const Sales_data & rhs);
std::multiset<Sales_data, compareType> bookstore(compareIsbn);
std::multiset<Sales_data,compareType>::iterator c_it = boolstore.begin();
练习 11.20:
重写 11.1 节练习(第 376 页)的单计数程序,使用 insert代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。
解答:
#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
map<string, size_t> word_count;
string word;
while (cin >> word)
{
auto ret = word_count.insert({ word,1 });
if (!ret.second)
++ret.first->second;
}
for (const auto &w : word_count)
cout << w.first << " occurs " << w.second << (w.second > 1 ? " times" : " time") << endl;
system("pause");
return 0;
}
练习 11.21:
假定 word_count 是一个 string 到 size_t 的 map,word 是一个 string,解释下面循环的作用:
while( cin >> word )
++word_count.insert({word,0}).first->second;
解答:
如果输入word,将 word 添加到 map 中,并首先将其计数器置为零,同时递增单词 word 对应的计数器。
练习11.22:
给定一个 ,map<string, vector<int>>,对容器的插入一个元素的 insert 版本,写出其参数类型和返回类型。
解答:
插入的参数类型: pair<std::string, std::vector<int>>
返回类型: pair< map<string, vector<int>>::iterator, bool > 返回值是一个 pair,第一个为 map 类型的迭代器,第二个为 bool 。
练习 11.23:
11.2.1 节练习(第 378 页)中的 map 以孩子的姓为关键字,保存他们的名的 vector,用 multimap 重写此 map。
解答:
#include<iostream>
#include<string>
#include<vector>
#include<map>
using namespace std;
int main()
{
multimap<string, string> fam;
string fam_name, chil_name;
cout << "Please input the new family members: " << endl;
while (cin >> chil_name >> fam_name)
{
fam.emplace(chil_name, fam_name);
}
cout << "====================" << endl;
for (auto &s : fam)
{
cout << s.second << " " << s.first << endl;
}
cout << "====================" << endl;
system("pause");
return 0;
}
练习 11.24:
下面的程序完成什么功能?
map< int, int > m;
m[0] = 1;
解答:
在 m 中寻找元素 “0” ,并将其计数器置为 1,若 m 中不存在 0 元素,则将 0 添加到 m 中,并将计数器置为 1。
练习 11.25:
对比下面程序与上一题程序
vector<int> v;
v[0] = 1;
解答:
将容器 v 中的 0 号元素赋值为 1
练习 11.26:
可以用什么类型来对一个 map 进行下标操作?下标运算符返回的类型是什么?请给出一个具体例子——即,定义一个 map,然后写出一个可以用来对 map 进行下标操作的类型及下标运算符返回的类型。
解答:
#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
map<string, size_t> word_count;
word_count["the"] = 3;
cout << word_count["the"] << endl;
system("pause");
return 0;
}
下标运算符返回的类型是 size_t。
练习 11.27:
对于什么问题你会使用 count 来解决?什么时候你又会选择 find 呢?
解答:
对于允许重复元素出现的容器,需要对查找的关键字进行计数时,选择 count,而对于不允许重复元素出现的容器,或者不需要要对元素进行计数操作时,选择 find。
练习 11.28:
对一个 string 到 int 的 vector 的 map,定义并初始化一个变量来保存在其上调用 find 的返回结果。
解答:
#include<iostream>
#include<string>
#include<vector>
#include<map>
using namespace std;
int main()
{
map<string, vector<int>> m = { {"the", {2,4,6,8,12}}, {"quick",{ 1,3,5,7,9 }} };
auto it = m.find("the");
for (const auto &c : it->second)
cout << c << " ";
cout << endl;
system("pause");
return 0;
}
练习 11.29:
如果给定的关键字不在容器中, upper_bound、lower_bound、和 equal_bound 分别会返回什么?
解答:
upper_bound 和 lower_bound 会返回相等的迭代器,并且都指向某个关键字的插入点,不影响容器中元素的顺序。
equal_bound 则返回一个迭代器 pair,pair 的两个成员均等于 c.end()。
练习 11.30:
对于本节最后一个程序中的输出表达式,解释运算对象 pos.first->second 的含义。
解答:
pos:pair 迭代器。
pos.first:pair 的第一个成员,指向一个与关键字匹配的元素的迭代器。
pos.first->second: 解引用此迭代器,获取指向 pair 中的第一个与关键字匹配的元素的 second 成员。
练习 11.31:
编写程序,定义一个作者及其作品的 multimap。使用 find 在 multimap 中查找一个元素并用 erase 删除它。 确保你的程序在元素不在 map 中时也能正常运行。
解答:
#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
multimap<string, string> m;
m = { {"Anna", "anna"}, {"Marry", "marry"}, {"Anna", "anna2"}, {"John", "john"} };
string author, word;
cout << "Please input the author's name you want to delete: " << endl;
cin >> author >> word;
for (auto iter = m.find(author); iter != m.cend() && iter->first == author;)
{
if (iter->second == word)
iter = m.erase(iter);
else
++iter;
}
cout << "====================" << endl;
for (auto &c : m)
cout << c.first << " " << c.second << endl;
cout << endl;
system("pause");
return 0;
}
练习 11.32:
使用上一题定义的 multimap 编写一个程序,按字典序打印作者列表和他们的作品。
解答:
#include<iostream>
#include<string>
#include<map>
#include<set>
using namespace std;
int main()
{
multimap<string, string> m;
m = { {"Anna", "Fly"}, {"Marry", "Red"}, {"John", "Fox"}, {"Anna", "Jump"}, {"John", "Bird"},
{"John", "Bad Man"}, {"King", "Steve"}, {"Bob", "Brother"} };
map<string, multiset<string>> new_m;
for (auto &c : m)
new_m[c.first].insert(c.second);
cout << "==============================" << endl;
for (auto &s : new_m)
{
cout << s.first << ":";
for (auto &ss : s.second)
cout << " " << ss << ",";
cout << endl;
}
cout << "==============================" << endl;
system("pause");
return 0;
}
练习 11.33:
实现你自己版本的单词转换程序。
解答:
#include<iostream>
#include<sstream>
#include<fstream>
#include<string>
#include<map>
using namespace std;
const string & transform(const string &s, const map<string, string> &m)
{
auto map_it = m.find(s);
if (map_it != m.cend()) // 如果单词在转换规则中
return map_it->second; // 使用短语替换
else
return s; // 否则直接返回该单词
}
map<string, string> buildmap(ifstream &map_file)
{
map<string, string> trans_map; //保存转换规则
string key;
string value;
while (map_file >> key && getline(map_file, value))
{
if (value.size() > 1)
trans_map[key] = value.substr(1); //跳过前导空格
else
throw runtime_error("no rule for " + key);
}
return trans_map;
}
void word_transform(ifstream &map_file, ifstream &input)
{
auto trans_map = buildmap(map_file); //保存转换规则
string text; //保存输入中的每一行
while (getline(input, text)) //读取一行输入
{
istringstream stream(text); //读取每个单词
string word;
bool firstword = true;
while (stream >> word)
{
if (firstword)
firstword = false;
else
cout << " "; //如果不是首单词,则在单词前加一个空格
cout << transform(word, trans_map); // 打印输出
}
cout << endl;
}
}
int main()
{
ifstream rule("...\\rule.txt"),
text("...text.txt");
word_transform(rule, text);
system("pause");
return 0;
}
练习 11.34:
如果你将 transform 函数中的 find 替换为下标运算符,会发生什么情况?
解答:
下标运算符会当单词不在转换规则中时,将会执行将此单词添加到转换规则中的操作,但是 m 已经声明为了 const 型,不能写入新的值,因此会发生错误。
练习 11.35:
在 buildmap 中,如果进行如下改写,会有什么效果?
trans_map[key] = value.substr(1);
改为 trans_map.insert({key, value.substr(1)});
解答:
改为 insert 后,如果一个单词重复出现多次,循环只会将最先一个对应短语存入 trans_map,即重复单词不会重复插入。
练习 11.36:
我们的程序并没有检查输入文件的合法性。特别是,他假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,在与你的程序进行比较。
解答:
程序会出现异常。
修改如下:
#include<iostream>
#include<sstream>
#include<fstream>
#include<string>
#include<map>
using namespace std;
const string & transform(const string &s, const map<string, string> &m)
{
auto map_it = m.find(s);
if (map_it != m.cend()) // 如果单词在转换规则中
return map_it->second; // 使用短语替换
else
return s; // 否则直接返回该单词
}
map<string, string> buildmap(ifstream &map_file)
{
map<string, string> trans_map; //保存转换规则
string key;
string value;
while (map_file >> key && getline(map_file, value))
{
if (value.size() > 1)
trans_map[key] = value.substr(1).substr(0, value.find_last_not_of(' ')); //跳过前导空格
}
return trans_map;
}
void word_transform(ifstream &map_file, ifstream &input)
{
auto trans_map = buildmap(map_file); //保存转换规则
string text; //保存输入中的每一行
while (getline(input, text)) //读取一行输入
{
istringstream stream(text); //读取每个单词
string word;
bool firstword = true;
while (stream >> word)
{
if (firstword)
firstword = false;
else
cout << " "; //如果不是首单词,则在单词前加一个空格
cout << transform(word, trans_map); // 打印输出
}
cout << endl;
}
}
int main()
{
ifstream rule("...rule2.txt"),
text("...text.txt");
if (rule && text)
word_transform(rule, text);
else
cout << "Not found the right files!" << endl;
system("pause");
return 0;
}
练习 11.37:
一个无序容器与其有序版本相比有何优势?有序版本有何优势?
解答:
无序容器的优势:在关键字类型的元素没有明显的序关系的情况下,以及在某些应用中,维护元素的序代价非常高昂,此时使用无序容器更好。
有序容器的优势:迭代器可以访问元素,同时还可以定义关键字类型为自定义类类型的容器,但是无序容器只能定义关键字是内置类型(包括指针类型,string,智能指针类型的无序容器)。
练习 11.38:
用 unordered_map 重写单词计数程序(参见 11.1 节,第 375 页)和单词转换程序(参见 11.3.6 节,第391 页)。
解答:
单词计数程序:
#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
int main()
{
unordered_map<string, size_t> word_count;
string word;
cout << "Please input: " << endl;
while (cin >> word)
++word_count[word];
for (const auto &w : word_count)
cout << w.first << " occurs " << w.second << (w.second > 1 ? " times" : " time") << endl;
system("pause");
return 0;
}
与之前使用 map 编写的相同的单词计数程序相比,发现使用无序容器时,其不会按照字典顺序输出。
单词转换程序:
#include<iostream>
#include<sstream>
#include<fstream>
#include<string>
#include<map>
#include<unordered_map>
using namespace std;
const string & transform(const string &s, const unordered_map<string, string> &m)
{
auto map_it = m.find(s);
if (map_it != m.cend()) // 如果单词在转换规则中
return map_it->second; // 使用短语替换
else
return s; // 否则直接返回该单词
}
unordered_map<string, string> buildmap(ifstream &map_file)
{
unordered_map<string, string> trans_map; //保存转换规则
string key;
string value;
while (map_file >> key && getline(map_file, value))
{
if (value.size() > 1)
trans_map[key] = value.substr(1).substr(0, value.find_last_not_of(' ')); //跳过前导空格
}
return trans_map;
}
void word_transform(ifstream &map_file, ifstream &input)
{
auto trans_map = buildmap(map_file); //保存转换规则
string text; //保存输入中的每一行
while (getline(input, text)) //读取一行输入
{
istringstream stream(text); //读取每个单词
string word;
bool firstword = true;
while (stream >> word)
{
if (firstword)
firstword = false;
else
cout << " "; //如果不是首单词,则在单词前加一个空格
cout << transform(word, trans_map); // 打印输出
}
cout << endl;
}
}
int main()
{
ifstream rule("...rule.txt"),
text("...text.txt");
if (rule && text)
word_transform(rule, text);
else
cout << "Not found the right files!" << endl;
system("pause");
return 0;
}