描述 map 和 vector 的不同。
map 是关联容器,称为关联数组,其元素是按关键字-值对来保存和访问的,它的关键字是元素的下标;
vector 是顺序容器,其元素是按它们在容器中的位置来顺序保存和访问的,它的下标一定是数字。
分别给出最适合使用 list、vector、deque、map 以及 set 的例子。
list:需要多次在除首位位置插入元素;
vector:需要随机访问元素;
deque:需要随机访问元素,同时需要在首位增删元素;
map:保存具有映射的元素对,例如字典;
set:保存需要用来判断的元素集合,元素本身是唯一的关键字。
编写你自己的单词计数程序。
#include<iostream> #include<map> #include<string> using std::cin; using std::cout; using std::endl; using std::map; using std::string; 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 << " 出现了 " << w.second << " 次!" << endl; } return 0; }扩展你的程序,忽略大小写和标点。例如,“example.”、“example,” 和 “Example” 应该递增相同的计数器。
#include<iostream> #include<map> #include<set> #include<string> #include<algorithm> using namespace std; int main() { map<string, size_t> word_count; string word; while (cin>>word) { // 转换为小写 transform(word.begin(), word.end(), word.begin(), ::tolower); auto iter = word.end() - 1; if (ispunct(*iter)) { iter = word.erase(iter); } ++word_count[word]; } for(const auto &w:word_count) { cout << w.first << " 出现了 " << w.second << " 次!" << endl; } return 0; }解释 map 和 set 的区别。你如何选择使用哪个?
map包括关键字-值对; set只有关键字。
解释 set 和 list 的区别。你如何选择使用哪个?
set:是关联容器,查找较快,但不支持位置相关的操作; list:是顺序容器,查找关键字是和容器的大小有关系,支持位置相关的操作,功能更多。
定义一个 map,关键字是家庭的姓,值是一个 vector,保存家中孩子(们)的名。编写代码,实现添加新的家庭以及向已有家庭中添加新的孩子。
#include<iostream> #include<vector> #include<string> #include<map> using namespace std; int main() { map<string, vector<string>> family; string lName, fName; while (cin>> lName >> fName) { family[fName].push_back(lName); } for(const auto &f:family) { cout << f.first << " 家中有:" << endl; for(const auto &n:f.second) { cout << n << " "; } cout << endl; } return 0; }编写一个程序,在一个 vector 而不是一个 set 中保存不重复的单词。使用 set 的优点是什么?
#include<iostream> #include<vector> #include<string> #include<algorithm> using namespace std; int main() { vector<string> sv; string str; while (cin>>str) { if (find(sv.begin(),sv.end(),str)==sv.end()) { sv.push_back(str); } } for(const auto &s : sv) { cout << s << " "; } cout << endl; return 0; }vector 需要判断输入是否重复;set 不需要。
定义一个 map,将单词与一个行号的 list 关联,list 中保存的是单词所出现的行号。
map<string,list<size_t>> m可以定义一个 vector::iterator 到 int 的 map 吗?list::iterator 到 int 的 map 呢?对于两种情况,如果不能,解释为什么。
前者可以,后者不行。因为 vector 的迭代器支持“严格弱序”操作,即“ < ”,而 list 的迭代器不支持。
不使用 decltype 重新定义 bookstore。
multiset<Sales_data, bool (*pf)(const Sales_data &lhs, const Sales_data &rhs)> bookstore(compareIsbn);编写程序,读入 string 和 int 的序列,将每个 string 和 int 存入一个 pair 中,pair 保存在一个 vector 中。
#include<iostream> #include<vector> #include<string> #include<utility> using namespace std; int main() { string str; int i; vector<pair<string, int>> pv; while (cin>>str>>i) { pv.push_back(pair<string, int>(str, i)); } for(const auto &p:pv) { cout << p.first << " " << p.second << endl; } return 0; }在上一题的程序中,至少有三种创建 pair 的方法。编写此程序的三个版本,分别采用不同的方法创建 pair。解释你认为哪种形式最易于编写和理解,为什么?
#include<iostream> #include<vector> #include<string> #include<utility> using namespace std; int main() { string str; int i; vector<pair<string, int>> pv; while (cin>>str>>i) { // 使用成员进行初始化 pv.push_back(pair<string, int>(str, i)); // 直接隐式调用默认构造函数转化为pair // pv.push_back({str, i}); // 调用make_pair函数进行构造 // pv.push_back(make_pair(str, i)); // 使用成员进行初始化 // pair<string, int> p(str, i); // pv.push_back(p); // 使用成员进行初始化 // pair<string, int> p = {str, i}; // pv.push_back(p); } for(const auto &p:pv) { cout << p.first << " " << p.second << endl; } return 0; }直接隐式构造最利于编写,指定成员类型并进行初始化最易于理解。
扩展你在 11.2.1节练习(第378页)中编写的孩子姓到名的 map,添加一个 pair 的 vector,保存孩子的名和生日。
#include<iostream> #include<vector> #include<string> #include<map> #include<sstream> #include<utility> using namespace std; int main() { //map<string, vector<string>> family; map<string, vector<pair<string, string>>> family; string lName, fName, birthday; while (cin>> lName >> fName>>birthday) { family[fName].push_back({lName, birthday}); } for(const auto &f:family) { cout << f.first << " 家中有:" << endl; for(const auto &n:f.second) { cout << n.first << " 生日是:" << n.second << endl; } cout << endl; } return 0; }对一个 int 到 vector< int > 的 map,其 mapped_type、key_type 和 value_type 分别是什么?
mapped_type:vector< int >
key_type:int
value_type:pair<const int, vector< int >>
使用一个 map 迭代器编写一个表达式,将一个值赋予一个元素。
#include<iostream> #include<map> #include<string> using namespace std; int main() { map<int, string> sm{{1, "one"}}; map<int, string>::iterator iter = sm.begin(); iter->second = "two"; for(const auto &n:sm) { cout << n.first << " " << n.second << endl; } return 0; }假定 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.end())); copy(c.begin(), c.end(), back_inserter(v));(1)合法。将 v 中的元素依次插入到 c 的尾部;
(2)不合法。back_inserter 需要调用 push_back() 关联容器不支持;
(3)和(4)合法。将 c 中的元素依次插入 v 的尾部;
写出第 382 页循环中 map_it 的类型,不要使用 auto 或 decltype。
map<string, size_t>::iterator定义一个变量,通过对 11.2.2 节(第 378 页)中的名为 bookstore 的 multiset 调用 begin() 来初始化这个变量。写出变量的类型,不要使用 auto 或 decltype。
multiset<Sales_data, bool (*pf)(const Sales_data &lhs, const Sales_data &rhs)>:: iterator bookstore_iter = bookstore.begin();重写 11.1 节练习(第 376 页)的单词计数程序,使用 insert 代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。
#include<iostream> #include<map> #include<string> using namespace std; int main() { map<string, size_t> word_count; string word; while (cin>>word) { // ++word_count[word]; auto ret = word_count.insert({word, 1}); if (!ret.second) { ++(ret.first->second); } } for(const auto &w:word_count) { cout << w.first << " 出现了 " << w.second << " 次!" << endl; } return 0; }下标版本更容易编写和理解。insert 版本还要手动判断插入元素是否已经存在。
假定 word_count 是一个 string 到 size_t 的 map,word 是一个 string,解释下面循环的作用:
while (cin >> word) ++word_count.insert({word, 0}).first->second;insert 插入 word,并将它的计数值置为 0,返回一个 pair,pair 的 first 是插入元素的迭代器,迭代器指向一个 pair,pair 的 second 是这个 word 对应的计数值,即 0,最后将其自增结果为 1,表示插入元素并且计数为 1。
给定一个 map<string,vector< int >>,对此容器的插入一个元素的 insert 版本,写出其参数类型和返回类型。
参数类型:
pair <string, vector< int >>返回类型:
pair <map<string, vector< int >>::iterator, bool>11.2.1 节练习(第 378 页)中的 map 以孩子的姓为关键字,保存他们的名的 vector,用 multimap 重写此 map。
#include<iostream> #include<vector> #include<string> #include<map> using namespace std; int main() { multimap<string, vector<string>> family; string lName, fName; while (cin>> lName >> fName) { vector<string> lNames; lNames.push_back(lName); family.insert({fName, lNames}); } for(const auto &f:family) { cout << f.first << " 家中有:" << endl; for(const auto &n:f.second) { cout << n << " "; } cout << endl; } return 0; }下面的程序完成什么功能?
map<int, int> m; m[0] = 1;检查 m 中是否有关键字为 0 的键-值对:如果有,将它的值赋值为1;如果没有,添加关键字 0,并赋值为1.
对比下面的程序与上一题程序
vector<int> v; v[0] = 1;没有初始化,不能自动插入元素。
可以用什么类型来对一个 map 进行下标操作?下标运算符返回的类型是什么?请给出一个具体例子——即,定义一个 map,然后写出一个可以用来对 map 进行下标操作的类型以及下标运算符将会返会的类型。
#include<iostream> #include<map> #include<string> using namespace std; int main() { map<int, string> ism{{0,"a"}}; int i = 0; string str = ism[i]; cout << str << endl; map<int, string>::key_type j = 0; map<int, string>::mapped_type mstr = ism[j]; cout << mstr << endl; return 0; }对于什么问题你会使用 count 来解决?什么时候你又会选择 find 呢?
要检查特定元素的关键字是否在容器中用 find; 如果是要统计指定元素的关键字的数量用 count。
对一个 string 到 int 的 vector 的 map,定义并初始化一个变量来保存在其上调用 find 所返回的结果。
#include<iostream> #include<string> #include<vector> #include<map> using namespace std; int main() { map<string, vector<int>> s_ivm = {{"a", {1, 1, 1, 1}}}; map<string, vector<int>>::iterator iter = s_ivm.find("a"); cout << (iter->second)[0] << endl; return 0; }如果给定的关键字不在容器中,upper_bound、lower_bound 和 equal_range 分别会返回什么?
如果关键词不存在,upper_bound 和 lower_bound 的返回会指向同一个迭代器;equal_range 的返回迭代器 pair 中的两个迭代器会相等。
对于本节最后一个程序中的输出表达式,解释运算对象 pos.first->second 的含义。
pos 保存迭代器对,表示与关键字匹配的元素范围;
pos.first 是匹配范围的开头迭代器,指向一个 mapped_value;
pos.first->second 是解引用键值对中的值。
编写程序,定义一个作者及其作品的 multimap。使用 find 在 multimap 中查找一个元素并用 erase 删除它。确保你的程序在元素不在 map 中时也能正常运行。
#include<iostream> #include<map> #include<string> using namespace std; int main() { multimap<string, string> ssmm = {{"Mike", "aaa"}, {"Jone", "asas"}, {"Mike", "bbb"}}; ssmm.erase(ssmm.find("Jone")); for(const auto &m:ssmm) { cout << m.first << " " << m.second << endl; } return 0; }使用上一题定义的 multimap 编写一个程序,按字典序打印作者列表和他们的作品。
#include<iostream> #include<map> #include<string> #include<set> using namespace std; int main() { multimap<string, string> ssmm = {{"Mike", "bbb"}, {"Mike", "aaa"}, {"Jone", "asas"}}; map<string, set<string>> authors; for(const auto a:ssmm) { authors[a.first].insert(a.second); } for(const auto &m:authors) { cout << m.first << ":"; for(const auto & a:m.second) { cout << a << " "; } cout << endl; } return 0; }实现你自己版本的单词转换程序。
#include<iostream> #include<string> #include<map> #include<fstream> #include<sstream> using namespace std; 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("没有对 " + key + "的转换"); } } return trans_map; } 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; } } 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 word_file("word_transform_rule.txt"), input("word_text.txt"); word_transform(word_file, input); return 0; }如果你将 transform 函数中的 find 替换为下标运算符,会发生什么情况?
遇到转换规则里没有的单词会插入到规则 map 中,并初始化为空。
在 buildMap 中,如果进行如下改写,会有什么效果?
trans_map[key] = value.substr(1); 改为trans_map.insert({key, value.substr(1)});本程序中没有影响,如果关键字有多次出现,下标操作会将保留最后一次出现的值;而 insert 操作只会保留第一次的值。
我们的程序并没检查输入文件的合法性。特别是,它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格,然后就结束了,会发生什么?预测程序的行为并进行验证,再与你的程序进行比较。
if (value.size() > 1) { trans_map[key] = value.substr(1); } else { throw runtime_error("没有对 " + key + "的转换"); }只有一个空格,则 value.size() =1 会抛出异常!
一个无序容器与其有序版本相比有何优势?有序版本有何优势?
无序容器能获得更好的平均性能;
有序容器可以定义元素排序。
用 unordered_map 重写单词计数程序(参见 11.1 节,第 375 页)和单词转换程序(参见 11.3.6 节,第 391 页)。
#include<iostream> #include<unordered_map> #include<string> using namespace std; int main() { unordered_map<string, size_t> word_count; string word; while (cin>>word) { ++word_count[word]; } for(const auto &w:word_count) { cout << w.first << " " << w.second << endl; } return 0; } #include<iostream> #include<string> #include<unordered_map> #include<fstream> #include<sstream> using namespace std; unordered_map<string, string> buildunordered_map(ifstream &unordered_map_file) { unordered_map<string, string> trans_unordered_map; string key; string value; while (unordered_map_file>>key&&getline(unordered_map_file,value)) { if (value.size() > 1) { trans_unordered_map[key] = value.substr(1); } else { throw runtime_error("没有对 " + key + "的转换"); } } return trans_unordered_map; } const string & transform(const string &s, const unordered_map<string, string> &m) { auto unordered_map_it = m.find(s); if (unordered_map_it!=m.cend()) { return unordered_map_it->second; } else { return s; } } void word_transform(ifstream &unordered_map_file, ifstream &input) { auto trans_unordered_map = buildunordered_map(unordered_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_unordered_map); } cout << endl; } } int main() { ifstream word_file("word_transform_rule.txt"), input("word_text.txt"); word_transform(word_file, input); return 0; }