《C++ Primer》第12章 动态内存

12.3节使用标准库:文本查询程序 习题答案

练习12.27:TextQuery和QueryResult类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。

【出题思路】

本题综合练习已学过的知识实现文本查询程序。

【解答】

#ifndef PROGRAM12_27_H
#define PROGRAM12_27_H#include <vector>
#include <string>
#include <fstream>
#include <memory>
#include <map>
#include <set>
#include <sstream>
#include <algorithm>
#include <iterator>using std::shared_ptr;
using std::vector;
using std::string;
using std::ifstream;class QueryResult;
class TextQuery
{
public:using LineNo = vector<string>::size_type;TextQuery(ifstream &);QueryResult query(const string &) const;private:shared_ptr<vector<string>> input;std::map<string, shared_ptr<std::set<LineNo>>> result;
};class QueryResult
{
public:friend std::ostream& print(std::ostream&, const QueryResult&);public:QueryResult(const string& s, shared_ptr<std::set<TextQuery::LineNo>> set, shared_ptr<vector<string>> v):word(s), nos(set), input(v){}private:string word;shared_ptr<std::set<TextQuery::LineNo>> nos;shared_ptr<vector<string>> input;
};TextQuery::TextQuery(std::ifstream& ifs):input(new vector<string>)
{LineNo lineNo{0};for(string line; std::getline(ifs, line); ++lineNo){input->push_back(line);std::istringstream line_stream(line);for(string text, word; line_stream >> text; word.clear()){//避免读一个单词后跟标点符号(如:word,)std::remove_copy_if(text.begin(), text.end(), std::back_inserter(word), ispunct);//use reference avoid count of shared_ptr add.auto &nos = result[word];if(!nos) nos.reset(new std::set<LineNo>);nos->insert(lineNo);}}
}QueryResult TextQuery::query(const string& str) const
{//使用静态只分配一次。static shared_ptr<std::set<LineNo>> nodate(new std::set<LineNo>);auto found = result.find(str);if(found == result.end()){return QueryResult(str, nodate, input);}else{return QueryResult(str, found->second, input);}
}std::ostream& print(std::ostream& out, const QueryResult& qr)
{out << qr.word << "  occurs  " << qr.nos->size()<< (qr.nos->size() > 1? " times" : " time") << std::endl;for(auto i : *qr.nos){out << "\t(line " << i + 1 << ") " << qr.input->at(i) << std::endl;}return out;
}#endif // PROGRAM12_27_H
#include <iostream>
#include "program12_27.h"using std::cout;
using std::endl;void runQueries(std::ifstream& infile)
{TextQuery tq(infile);while(true){std::cout << "enter word to lock for, or q to quit:" << endl;string s;if(!(std::cin >> s) || (s == "q"))break;print(std::cout, tq.query(s)) << std::endl;}
}int main(int argc, const char* argv[])
{std::ifstream in(argv[1]);if(!in){cout << "无法打开输入文件" << endl;return -1;}runQueries(in);return 0;
}

data12_27.txt文件内容为:

Alice Emma has long flowing red hair.
Her Daddy says when the wind blows
through her hair, it looks almost alive,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
"Daddy, shush, there is no such thing,"
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

设置命令行参数:

运行结果:

练习12.28:编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用vector、map和set容器保存来自文件的数据并生成查询结果。

【出题思路】

采用过程式程序设计而非面向对象的程序设计来解决这个问题,并体会两者的差异。

【解答】

总体设计思路与面向对象的版本相似,但有一些差异:

1.由于不用类来管理数据,file和wm都定义为全局变量,便于在函数间共享。当然也可以定义为局部变量,通过函数参数传递。

2.由于不必进行不同类对象间的数据共享,因此file和wm中的set都不必用shared_ptr管理,直接定义为vector和set即可。使用它们的代码也要相应修改。

3.由于不用类来保存查询结果,因此将query和print函数合二为一。

#include <cstddef>
using std::size_t;#include <string>
using std::string;#include <iostream>
using std::cout;
using std::endl;#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H// return the plural version of word if ctr is greater than 1
inline
string make_plural(size_t ctr, const string &word,const string &ending)
{return (ctr > 1) ? word + ending : word;
}#endif // MAKE_PLURAL_H
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <cstdlib> //要使用EXIT_FAILURE#include "make_plural.h"using namespace std;using line_no = vector<string>::size_type;
vector<string> file;            //文件每行内容
map<string, set<line_no>> wm;   //单词到行号set的映射string cleanup_str(const string &word)
{string ret;for(auto it = word.begin(); it != word.end(); ++it){if(!ispunct(*it))ret += tolower(*it);}return ret;
}void input_text(ifstream &is)
{string text;while(getline(is, text))                    //对文件中每一行{file.push_back(text);                   //保存此行文本unsigned long n = file.size() - 1;      //当前行号istringstream line(text);               //将行文本分解为单词string word;while(line >> word)                     //对行中每个单词{//将当前行号插入到其行中与set中,如果单词不在wm中,以之为下标在vm中添加一项wm[cleanup_str(word)].insert(n);}}
}ostream &query_and_print(const string &sought, ostream &os)
{//使用find而不是下标运算符来查找单词,避免将单词添加到wm中!auto loc = wm.find(sought);if(loc == wm.end())             //未找到{os << sought << "出现了0次" << endl;}else{auto lines = loc->second;   //行号setos << sought << "出现了" << lines.size() << "次" << endl;for(auto num: lines)        //打印单词出现的每一行{os << "\t(第" << num + 1 << "行)" << *(file.begin() + num) << endl;}}return os;
}void runQueries(ifstream &infile)
{//infile是一个ifstream,指向我们要查询的文件input_text(infile);//读入文本并建立查询map//与用户交素养:提示用户输入要查询的单词,完成查询并打印结果while(true){cout << "enter word to look for, or q to quit:" << endl;string s;//若遇到文件尾或用户输入了q时循环终止if(!(cin >> s) || s == "q")break;//指向查询并打印结果query_and_print(s, cout) << endl;}
}//程序接受唯一的命令行参数,表示文件文件名
int main(int argc, const char * argv[])
{//打开要查询的文件ifstream infile;//打开文件失败败,程序异常退出if(argc < 2 || !(infile.open(argv[1]), infile)){cerr << "No input file!" << endl;return EXIT_FAILURE;}runQueries(infile);cout << "hello world" << endl;return 0;
}

使用上题的数据文件data12_27.txt文件,设置命令行参数,运行结果如下:

练习12.29:我们曾经用do while循环来编写管理用户交互的循环(参见5.4.4节,第169页)。用do while重写本节程序,解释你倾向于哪个版本,为什么。

【出题思路】

采用过程式程序设计而非面向对象的程序设计来解决这个问题,并体会两者的差异。

【解答】

循环改成如下形式即可:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <cstdlib>  //要使用EXIT_FAILUREusing namespace std;using line_no = vector<string>::size_type;
vector<string> file;//文件每行内容
map<string, set<line_no>> wm;//单词到行号set的映射string cleanup_str(const string &word)
{string ret;for(auto it = word.begin(); it != word.end(); ++it){if(!ispunct(*it))ret += tolower(*it);}return ret;
}void input_text(ifstream &is)
{string text;while(getline(is, text))                    //对文件中每一行{file.push_back(text);                   //保存此行文本unsigned long n = file.size() - 1;      //当前行号istringstream line(text);               //将行文本分解为单词string word;while(line >> word)                     //对行中每个单词{//将当前行号插入到其行中与set中,如果单词不在wm中,以之为下标在vm中添加一项wm[cleanup_str(word)].insert(n);}}
}ostream &query_and_print(const string &sought, ostream &os)
{//使用find而不是下标运算符来查找单词,避免将单词添加到wm中!auto loc = wm.find(sought);if(loc == wm.end())             //未找到{os << sought << "出现了0次" << endl;}else{auto lines = loc->second;   //行号setos << sought << "出现了" << lines.size() << "次" << endl;for(auto num: lines)        //打印单词出现的每一行{os << "\t(第" << num + 1 << "行)" << *(file.begin() + num) << endl;}}return os;
}void runQueries(ifstream &infile)
{//infile是一个ifstream,指向我们要查询的文件input_text(infile);//读入文本并建立查询map//与用户交素养:提示用户输入要查询的单词,完成查询并打印结果do//显示,由于循环中的执行步骤是“输入——检查循环条件——执行查询”,检查循环条件是中间步骤,因此,while和do while没有什么差别,不会比另一个更简洁{cout << "enter word to look for, or q to quit:" << endl;string s;//若遇到文件尾或用户输入了q时循环终止if(!(cin >> s) || s == "q")break;//指向查询并打印结果query_and_print(s, cout) << endl;}while(true);
}//程序接受唯一的命令行参数,表示文件文件名
int main(int argc, const char * argv[])
{//打开要查询的文件ifstream infile;//打开文件失败败,程序异常退出if(argc < 2 || !(infile.open(argv[1]), infile)){cerr << "No input file!" << endl;return EXIT_FAILURE;}runQueries(infile);cout << "hello world" << endl;return 0;
}

设置命令行参数,运行结果如下:

练习12.30:定义你自己版本的TextQuery和QueryResult类,并执行12.3.1节(第431页)中的runQueries函数。

【出题思路】

本题综合练习已学过的知识实现文本查询程序。

【解答】

解答同练习12.27。

练习12.31:如果用vector代替set保存行号,会有什么差别?哪种方法更好?为什么?

【出题思路】

理解vector和set的差异。

【解答】

对这个问题而言,vector更好。因为,虽然vector不会维护元素值的序,set会维护关键字的序,但注意到,我们是逐行读取输入文本的,因此每个单词出现的行号是自然按升序加入到容器中的,不必特意用关联容器来保证行号的升序。而从性能角度,set是基于红黑树实现的,插入操作时间复杂性为O(logn)(n为容器中元素数目),而vector的push_back可达到常量时间。另外,一个单词在同一行中可能出现多次。set自然可保证关键字不重复,但对vector这也不成为障碍——每次添加行号前与最后一个行号比较一下即可。总体性能仍然是vector更优。

练习12.32:重写TextQuery和QueryResult类,用StrBlob代替vector<string>保存输入文件。

【出题思路】

本题练习在较大程序中配合使用StrBlob和StrBlobPtr来代替用shared_ptr管理的vector<string>及迭代器。

【解答】

对my_QueryResult.h、my_TextQuery.h和my_StrBlob.h

相对于书中的原程序进行如下修改:

1.在my_QueryResult.h中包含头文件my_StrBlob.h。

2.QueryResult类的file成员改为StrBlob类型,相应的,构造函数的第三个参数和成员函数get_file的返回类型也都改为StrBlob类型。

3.类似的TextQuery类的成员file也改为StrBlob类型。

4.由于file不再是shared_ptr而是StrBlob,TextQuery构造函数(my_TextQuery.cpp)中的“file->”均改为“file.”。

5.在原来的代码中,TextQuery构造函数动态分配了一个vector<string>,用其指针初始化file成员(shared_ptr)。但StrBlob类并未定义接受vector<string> *的构造函数,因此我们在my_StrBlob.h文件中为其添加了这个构造函数,用指针参数直接初始化data成员(shared_ptr)。

6.在函数print(my_TextQuery.cpp)中,用file->begin()获得了vector的首位置迭代器,对其进行加法操作获得了指向第num个string的迭代器,最后通过解引用获得了这个string,将其打印出来。但StrBlobPtr只定义递增和递减操作,并未定义加法运算。因此,我们为其增加了my_StrBlob.h接受一个整型参数off的deref操作,能解引用出距当前位置curr偏移量为off的元素(但并不会修改curr的值)。

至此,所需要的修改进行完毕。

可以看到,我们对使用文本查询类的主程序未进行任何修改!读者可好好体会面向对象程序设计将接口和实现分离的优点。

#include <cstddef>
using std::size_t;#include <string>
using std::string;#include <iostream>
using std::cout;
using std::endl;#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H// return the plural version of word if ctr is greater than 1
inline
string make_plural(size_t ctr, const string &word,const string &ending)
{return (ctr > 1) ? word + ending : word;
}#endif // MAKE_PLURAL_H
#ifndef SYSTRBLOB_32_H
#define SYSTRBLOB_32_H
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>using namespace std;//提前声明,StrBlob中的友类声明所需
class StrBlobPtr;class StrBlob
{friend class StrBlobPtr;
public:typedef vector<string>::size_type size_type;StrBlob();StrBlob(initializer_list<string> i1);StrBlob(vector<string> *p);size_type size() const { return data->size(); }bool empty() const { return data->empty(); }//添加和删除元素void push_back(const string &t) { data->push_back(t); }void pop_back();//元素访问string& front();const string& front() const;string& back();const string& back() const;//提供给StrBlobPtr的接口StrBlobPtr begin(); //定义StrBlobPtr后才能定义这两个函数StrBlobPtr end();//const版本StrBlobPtr begin() const;StrBlobPtr end() const;private:shared_ptr<std::vector<std::string>> data;//如果data[i]不合法,抛出一个异常void check(size_type i, const std::string &msg) const;
};inline StrBlob::StrBlob(): data(make_shared<vector<string>>())
{}inline StrBlob::StrBlob(initializer_list<string> i1):data(make_shared<vector<string>>(i1))
{}inline StrBlob::StrBlob(vector<string> *p):data(p)
{}inline void StrBlob::check(size_type i, const string &msg) const
{if(i >= data->size())throw out_of_range(msg);
}inline string& StrBlob::front()
{//如果vector为空,check会抛出一个异常check(0, "front on empty StrBlob");return data->front();
}//const版本front
inline const string& StrBlob::front() const
{check(0, "front on empty StrBlob");return data->front();
}inline string& StrBlob::back()
{check(0, "back on empty StrBlob");return data->back();
}//const版本back
inline const string& StrBlob::back() const
{check(0, "back on empty StrBlob");return data->back();
}inline void StrBlob::pop_back()
{check(0, "pop_back on empty StrBlob");data->pop_back();
}//当试图访问一个不存在的元素时,StrBlobPtr抛出一个异常
class StrBlobPtr
{friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:StrBlobPtr():curr(0){}StrBlobPtr(StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz){}StrBlobPtr(const StrBlob &a, size_t sz = 0):wptr(a.data), curr(sz){}string& deref() const;string& deref(int off) const;StrBlobPtr& incr();//前缀递增StrBlobPtr& decr();//前缀递减private://若检查成功,check返回一个指向vector的shared_ptrshared_ptr<vector<string>> check(size_t, const string&) const;//保存一个weak_ptr,意味着底层vector可能会被销毁weak_ptr<vector<string>> wptr;size_t curr;//在数组中的当前位置
};inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{auto ret = wptr.lock();//vector还存在吗?if(!ret){throw runtime_error("unbound StrBlobPtr");}if(i >= ret->size()){throw out_of_range(msg);}return ret;//否则,返回指向vector的shared_ptr;
}inline string& StrBlobPtr::deref() const
{auto p = check(curr, "dereference past end");return (*p)[curr]; //(*p)是对象所指向的vector
}inline string& StrBlobPtr::deref(int off) const
{auto p = check(curr + off, "dereference past end");return (*p)[curr + off];//(*p)是对象所指向的vector
}//前缀递增:返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{//如果curr已经指向容器的尾后位置,就不能递增它check(curr, "increment past end of StrBlobPtr");++curr;//推进当前位置return *this;
}//前缀递减:返回递减后的对象的引用
inline StrBlobPtr& StrBlobPtr::decr()
{//如果curr已经为0, 递减它就会产生一个非法下标--curr;//递减当前位置check(-1, "decrement past begin of StrBlobPtr");return *this;
}//StrBlob的begin和end成员的定义
inline StrBlobPtr StrBlob::begin()
{return StrBlobPtr(*this);
}inline StrBlobPtr StrBlob::end()
{auto ret = StrBlobPtr(*this, data->size());return ret;
}//const版本
inline StrBlobPtr StrBlob::begin() const
{return StrBlobPtr(*this);
}inline StrBlobPtr StrBlob::end() const
{auto ret = StrBlobPtr(*this, data->size());return ret;
}//StrBlobPtr的比较操作
inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{auto l = lhs.wptr.lock(), r = rhs.wptr.lock();//若底层的vector是同一个if(l == r){//则两个指针都是空,或都是指向相同元素时,它们相等return (!r || lhs.curr == rhs.curr);}else{return false;//若指向不同vector,则不可能相等}
}inline bool neq(const StrBlobPtr lhs, const StrBlobPtr &rhs)
{return !eq(lhs, rhs);
}#endif // SYSTRBLOB_32_H
#ifndef QUERYRESULT_32_H
#define QUERYRESULT_32_H#include <vector>
#include <string>
#include <set>
#include <memory>
#include <iostream>
#include "SYStrBlob_32.h"class QueryResult
{friend std::ostream& print(std::ostream&, const QueryResult&);
public:typedef std::vector<std::string>::size_type line_no;typedef std::set<line_no>::const_iterator line_it;QueryResult(std::string s, std::shared_ptr<std::set<line_no>> p, StrBlob f):sought(s), lines(p), file(f){}std::set<line_no>::size_type size() const{return lines->size();}line_it begin() const{return lines->cbegin();}line_it end() const{return lines->cend();}StrBlob get_file(){return file;}private:std::string sought;//要查询的单词std::shared_ptr<std::set<line_no>> lines;//单词出现的行号的集号StrBlob file;//输入文件
};std::ostream &print(std::ostream&, const QueryResult&);#endif // QUERYRESULT_32_H
#ifndef TEXTQUERY_32_H
#define TEXTQUERY_32_H#include <vector>
#include <string>
#include <map>
#include <memory>
#include <set>
#include <fstream>
#include "QueryResult_32.h"class QueryResult;//这个声明类是必须的,查询函数需返回QueryResult类型
class TextQuery
{
public:using line_no = std::vector<std::string>::size_type;TextQuery(std::ifstream&);QueryResult query(const std::string&) const;void display_map();//调试辅助函数;打印映射表
private:StrBlob file;//输入文件//将每个单词映射到它所出现的行号的集合std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;//规范文本:删除标点,并转换为小写static std::string cleanup_str(const std::string&);
};#endif // TEXTQUERY_32_H
#include "TextQuery_32.h"
#include "make_plural.h"#include <cstddef>
#include <memory>
#include <sstream>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cstring>
#include <utility>
#include <string>using std::size_t;
using std::shared_ptr;
using std::istringstream;
using std::string;
using std::getline;
using std::vector;
using std::map;
using std::set;
using std::cerr;
using std::cout;
using std::cin;
using std::ostream;
using std::endl;
using std::ifstream;
using std::ispunct;
using std::tolower;
using std::strlen;
using std::pair;//读取输入文件,建立映射
TextQuery::TextQuery(ifstream &is):file(new vector<string>)
{string text;while(getline(is, text))//读取文件的每一行{file.push_back(text);//保存读入的文本行unsigned long n = file.size() - 1;//当前行号istringstream line(text);//从行中分离出单词string word;while(line >> word)//对行中的每个单词{word = cleanup_str(word);//如果单词还未在wm中,使用下标操作将其添加进去auto &lines = wm[word];//lines 是一个shared_ptrif(!lines){lines.reset(new set<line_no>); //分配一个新的set}lines->insert(n);//插入当前行号}}
}//cleanup_str删除标点同,并将所有文本转换为小写形式,从而查询是大小写不敏感的
string TextQuery::cleanup_str(const string &word)
{string ret;for(auto it = word.begin(); it != word.end(); ++it){if(!ispunct(*it)){ret += tolower(*it);}}return ret;
}QueryResult TextQuery::query(const string &sought) const
{//如果未找到sought,将返回一个指向下面这个set的指针static shared_ptr<set<line_no>> nodata(new set<line_no>);//使用fine而不是下标操作的原因是避免将不在wm中的单词添加进去!auto loc = wm.find(cleanup_str(sought));if(loc == wm.end()){return QueryResult(sought, nodata, file);//未找到}else{return QueryResult(sought, loc->second, file);}
}ostream &print(ostream &os, const QueryResult &qr)
{//如果找到了单词,打印出现次数及所有出现的行号os << qr.sought << " occurs " << qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") << endl;//打印单词出现的每一行for(auto num: *qr.lines)//对set中每个元素{//不让用户对从0开始的文件本行号困惑os << "\t(line " << num + 1 << ") " << qr.file.begin().deref(num) << endl;}return os;
}//调用函数
void TextQuery::display_map()
{auto iter = wm.cbegin(), iter_end = wm.cend();//对map中的每个单词for(; iter != iter_end; ++iter){cout << "word: " << iter->first << " {";//以常量引用方式获取位置向量,避免拷贝auto text_locs = iter->second;auto loc_iter = text_locs->cbegin();auto loc_iter_end = text_locs->cend();//打印此单词出现的所有行号while(loc_iter != loc_iter_end){cout << *loc_iter;if(++loc_iter != loc_iter_end)cout << ", ";}cout << "}\t"; //此单词的输出列表结束}cout << endl;//结束整个map的输出
}
#include "TextQuery_32.h"
#include "make_plural.h"
#include <string>
#include <fstream>
#include <iostream>
#include <cstdlib>//包含EXIT_FAILURE的定义using std::string;
using std::ifstream;
using std::cin;
using std::cout;
using std::cerr;
using std::endl;void runQueries(ifstream &infile)
{//infile是一个ifstream,指向我们要查询的文件TextQuery tq(infile);//保存文件并创建映射表//程序主循环,提示用户输入一个单词,查询此单词并打印结果while(true){cout << "enter word to look for, or q to quit:" << endl;string s;//若遇到文件尾或用户输入了q时循环终止if(!(cin >> s) || s == "q")break;//执行查询并打印结果print(cout, tq.query(s)) << endl;}
}//程序接受唯一的命令行参数,表示文件文件名
int main(int argc, const char * argv[])
{//打开要查询的文件ifstream infile;//打开文件失败败,程序异常退出if(argc < 2 || !(infile.open(argv[1]), infile)){cerr << "No input file!" << endl;return EXIT_FAILURE;}runQueries(infile);cout << "hello world" << endl;return 0;
}

设置命令行参数,运行结果如下:

练习12.33:在第15章中我们将扩展查询系统,在QueryResult类中将会需要一些额外的成员。添加名为begin和end的成员,返回一个迭代器,指向一个给定查询返回的行号的set中的位置。再添加一个名为get_file的成员,返回一个shared_ptr,指向QueryResult对象中的文件。

【出题思路】

本题练习在较大的类中添加迭代器等功能。

【解答】

对于begin和end成员,希望返回行号set中的位置,因此直接调用lines的cbegin和cend即可。对于get_file,直接返回file成员即可。

#include <cstddef>
using std::size_t;#include <string>
using std::string;#include <iostream>
using std::cout;
using std::endl;#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H// return the plural version of word if ctr is greater than 1
inline
string make_plural(size_t ctr, const string &word,const string &ending)
{return (ctr > 1) ? word + ending : word;
}#endif // MAKE_PLURAL_H
#ifndef PROGRAM12_33_H
#define PROGRAM12_33_H#include <memory>
#include <string>
#include <map>
#include <set>
#include <fstream>
#include <vector>
#include <iostream>class QueryResult;//declaration needed for return type in the query function
class TextQuery
{
public:using line_no = std::vector<std::string>::size_type;TextQuery(std::ifstream&);QueryResult query(const std::string&) const;void display_map();//debugging aid: print the mapprivate:std::shared_ptr<std::vector<std::string>> file;//输入文件//maps each word to the set of the lines in which that word appearsstd::map<std::string, std::shared_ptr<std::set<line_no>>> wm;//canonicalizes text: removes punctuation and makes everything lower casestatic std::string cleanup_str(const std::string&);
};class QueryResult
{friend std::ostream& print(std::ostream&, const QueryResult&);
public:typedef std::vector<std::string>::size_type line_no;typedef std::set<line_no>::const_iterator line_it;QueryResult(std::string s, std::shared_ptr<std::set<line_no>> p, std::shared_ptr<std::vector<std::string>> f):sought(s), lines(p), file(f){}line_it begin() const { return lines->cbegin(); }line_it end() const { return lines->cend(); }std::shared_ptr<std::vector<std::string>> get_file() { return file; }private:std::string sought;//word this query representsstd::shared_ptr<std::set<line_no>> lines;//lines it's onstd::shared_ptr<std::vector<std::string>> file;//input file
};std::ostream &print(std::ostream&, const QueryResult&);#endif // PROGRAM12_33_H
#include <sstream>  //istringstream
#include "program12_33.h"
#include "make_plural.h"
#include <cstdlib> //for EXIT_FAILUREusing std::size_t;
using std::shared_ptr;
using std::istringstream;
using std::string;
using std::getline;
using std::vector;
using std::map;
using std::set;
using std::cerr;
using std::cout;
using std::cin;
using std::ostream;
using std::endl;
using std::ifstream;
//using std::ispunct;
//using std::tolower;
using std::strlen;
using std::pair;// read the input file and build the map of lines to line numbers
TextQuery::TextQuery(ifstream &is): file(new vector<string>)
{string text;while(getline(is, text)) // for each line in the file{file->push_back(text);// remember this line of textunsigned long n = file->size() - 1;// the current line numberistringstream line(text); // separate the line into wordsstring word;while(line >> word){word = cleanup_str(word);//if word isn't already in wm, subscripting adds a new entryauto &lines = wm[word];//lines is a shared_ptrif(!lines)//that pointer is null the first time we see word{lines.reset(new set<line_no>);// allocate a new set}lines->insert(n);// insert this line number}}
}// not covered in the book -- cleanup_str removes
// punctuation and converts all text to lowercase so that
// the queries operate in a case insensitive manner
string TextQuery::cleanup_str(const string &word)
{string ret;for(auto it = word.begin(); it != word.end(); ++it){if(!ispunct(*it)){ret += tolower(*it);}}return ret;
}QueryResult TextQuery::query(const string &sought) const
{// we'll return a pointer to this set if we don't find soughtstatic shared_ptr<set<line_no>> nodata(new set<line_no>);//use find and not a subscript to avoid adding words to wm!auto loc = wm.find(cleanup_str(sought));if(loc == wm.end()){return QueryResult(sought, nodata, file);}else{return QueryResult(sought, loc->second, file);}
}ostream &print(ostream &os, const QueryResult &qr)
{//if the word was found, print the count and all occurrencesos << qr.sought << " occurs " << qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") << endl;// print each line in which the word appearedfor(auto num: *qr.lines){// don't confound the user with text lines starting at 0os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << endl;}return os;
}// debugging routine, not covered in the book
void TextQuery::display_map()
{auto iter = wm.cbegin(), iter_end = wm.cend();//for each word in the mapfor(; iter != iter_end; ++iter){cout << "word: " << iter->first << " {";// fetch location vector as a const reference to avoid copying itauto text_locs = iter->second;auto loc_iter = text_locs->cbegin(), loc_iter_end = text_locs->cend();// print all line numbers for this wordwhile(loc_iter != loc_iter_end){cout << *loc_iter;if(++loc_iter != loc_iter_end)cout << ", ";}cout << "}\n";// end list of output this word}cout << endl; // finished printing entire map
}void runQueries(ifstream &infile)
{//infile is an ifstream that is the file we want to queryTextQuery tq(infile); // store the file and build the query map// iterate with the user: prompt for a word to find and print resultswhile(true){cout << "enter word to look for, or q to quit:" << endl;string s;// stop if we hit end-of-file on the input or if a 'q' is enteredif(!(cin >> s) || s == "q")break;// run the query and print the resultsprint(cout, tq.query(s)) << endl;}
}int main(int argc, const char * argv[])
{// open the file from which user will query wordsifstream infile;// open returns void, so we use the comma operator XREF(commaOp)// to check the state of infile after the openif (argc < 2 || !(infile.open(argv[1]), infile)) {cerr << "No input file!" << endl;return EXIT_FAILURE;}runQueries(infile);cout << "hello world" << endl;return 0;
}

运行结果:

《C++ Primer》第12章 12.3节习题答案相关推荐

  1. 软件工程 科学出版社 郑逢斌主编 第12章 软件实现 课后习题答案

    软件工程 科学出版社 郑逢斌主编 第12章 软件实现 课后习题答案 1. 简述程序设计语言的基本特征及分类. 程序设计语言,通常简称为编语言,是一组用来定义计算机程序的语法规则.它是一种被标准化的交流 ...

  2. 《C++ Primer》第14章 14.3节习题答案

    <C++ Primer>第14章 操作重载与类型转换 14.3节  算术和关系运算符  习题答案 练习14.13:你认为Sales_data类还应该支持哪些其他算术运算符(参见表4.1,第 ...

  3. 《C++ Primer》第13章 13.5节习题答案

    <C++ Primer>第13章 拷贝控制 13.5节 动态内存管理类 习题答案 练习13.39:编写你自己版本的StrVec,包括自己版本的reserve.capacity(参见9.4节 ...

  4. 《C++ Primer》第9章 9.3节习题答案

    <C++ Primer>第9章 顺序容器 9.3节顺序容器操作习题答案 练习9.18:编写程序,从标准输入读取string序列,存入一个deque中.编写一个循环,用迭代器打印deque中 ...

  5. 《C++ Primer》第15章 15.4节习题答案

    <C++ Primer>第15章 面向对象程序设计 15.4节 抽象基类 习题答案 练习15.15:定义你自己的Disc_quote和Bulk_quote. [出题思路]本题练习实现不同折 ...

  6. 《C++ Primer》第15章 15.2节习题答案

    <C++ Primer>第15章 面向对象程序设计 本章介绍了面向对象程序设计的两个重要概念:继承和动态绑定,包括: □●继承.基类.派生类的基本概念. □●虚函数和虚基类. □●继承中的 ...

  7. 《C++ Primer》第5章 5.2节习题答案

    <C++ Primer>第5章 语句 5.2节 语句作用域 练习5.4:说明下列例子的含义,如果存在问题,试着修改它. (a)while(string::iterator iter != ...

  8. C#程序设计第三版(李春葆)第12章文件操作课后习题答案

    编程题 (上机实验题在最后!) ----------------------------------------------分割线----------------------------------- ...

  9. C++ Primer - 5th Edition - 书中源代码 - 课后习题答案

    C++ Primer - 5th Edition - 书中源代码 - 课后习题答案 C++ Primer - 5th Edition - 书中源代码 - 课后习题答案 1. C++ Primer, 5 ...

  10. matlab第三章题目,第3章 MATLAB矩阵处理习题答案

    <第3章 MATLAB矩阵处理习题答案>由会员分享,可在线阅读,更多相关<第3章 MATLAB矩阵处理习题答案(3页珍藏版)>请在人人文库网上搜索. 1.第3章 MATLAB矩 ...

最新文章

  1. 为什么分布式一定要有消息队列?
  2. POJ3982 序列
  3. 一个Web Project引用多个Java Project在Eclipse下的配置--转载
  4. hive 添加UDF(user define function) hive的insert语句
  5. 怎么将SVG转成PNG(.NET工具包编写)
  6. 21岁就破解困扰人们300年难题的天才,却一生坎坷,怀才不遇,至死还得不到认可...
  7. 做一个关于我和她微信聊天记录的爱心词云图
  8. php 分支排序,php – 基于类别和分支的Mysql排名
  9. string字符串转xml_Java将字符串转换为XML文档和将XML文档转换为String
  10. 不学无数——JAVA中NIO再深入
  11. (转)BlackRock:全球最大资管公司如何一步步倒戈人工智能?
  12. Map与JSON数据之间的互相转化
  13. 各自然带代表植被_自然带气候植被关系
  14. 欢迎使用CSDN-markdown编辑器231
  15. java xms xmn_java堆内存JVM属性调优总结(-Xms -Xmx -Xmn -Xss)
  16. 会议室录播方案及录播设备推荐
  17. python注释可用于表明作者和版权信息_Python-注释帮助我们理解-No9
  18. 40个笑到抽筋的神回复,绝了!
  19. Python:实现floor向下取整算法(附完整源码)
  20. 中国Linux与微软斗法北京谋变,从水火不容到共生(转)

热门文章

  1. IT业界的十大经典谎言[转载]
  2. 纯代码实现的分割线____简单而有效的html画线代码hr实例,纯代码实现的分割线____简单而有效的html画线代码“hr”实例...
  3. Linux c语言sleep多线程while循环实验
  4. Nat Microbio | 上师大乔永利组-大豆疫霉与宿主之间的“营养争夺战”
  5. Uibot-Excel自动获取某个工作表的总行数、总列数,读取其全部内容
  6. 订购一个月产品,计算多久后到期
  7. 最小均方误差法(MMSE)估计
  8. 阿里开源(EasyExcel):使用Java将数据导出为Excel表格、带样式----》java web下载 Excel文件
  9. GreenDao3.0+库,轻松搞定安卓数据库操作
  10. global 仪表控件 无人机地面站_虚拟仪表在无人机地面站中的应用