#include<iostream>
#include<map>
#include<set>
#include<string>
#include<cstring>
#include<cstdlib>
#include<fstream>
#include<sstream>
using namespace std;
class QueryResult;
class TextQuery
{
public:
using line_no = vector<string>::size_type; // vector<string> 里面的下标都用line_no代替
TextQuery(ifstream&); // 构造函数
QueryResult query(const string& s)const; //查询一个单词是否存在的成员函数
private:
shared_ptr<vector<string>> file; // 指向一个动态开辟的vector<string>
map<string, shared_ptr<set<line_no>>> wm; // 一个map,first是string,second是智能指针指向一个set<int>
};
TextQuery::TextQuery(ifstream& is):file(new vector<string>()) // 构造函数
{
// 作用就是给这个TextQuery类的对象初始化,这是一个构造函数嘛,把成员file以及wm初始化
string text;
while (getline(is, text))
{
file->push_back(text); // 将文件中的每一行存入vector<string>中
int n = file->size() - 1; // 存入的这一行的行号
istringstream line(text); // 看不懂
string word;
while (line >> word) // 一行中的每个单词
{
auto& lines = wm[word];// lines是一个shared_ptr
if (!lines) // 在我们第一次遇到这个单词时,此指针为空
lines.reset(new set<line_no>); // 分配一个新的set
lines->insert(n); // 将此行号插入set中
}
}
}
QueryResult TextQuery::query(const string& sought)const
{
// 如果未找到sought,我们将返回一个指向此set的指针
static shared_ptr<set<line_no>> nodata(new set<line_no>);
// 使用find而不是下标运算符来查找单词,避免将单词添加到wm中
auto loc = wm.find(sought); //loc是一个迭代器,指向那对元素,first是一个string,second是一个智能指针,指向一个set<line_no>
if (loc == wm.end())
{
return QueryResult(sought, nodata, file); // 此处file是一个智能指针,传递的时候就非常方便了
}
else
{
return QueryResult(sought, (*loc).second, file); // 这里返回的第二个参数就是一个shared_prt<set<line_no>>类型的对象
}
}
class QueryResult //查询结果的类,只是用来保存这个文件,查询的单词,以及这个单词所匹配的set,那个TextQuery类中的map并没有传递,只是传了map的second:shared_ptr<set<line_no>>
{
friend ostream& print(ostream&, const QueryResult&);
public:
QueryResult(string s, shared_ptr<set<TextQuery::line_no>> p, shared_ptr<vector<string>> f)
:sought(s), lines(p), file(f) {}
private:
string sought; // 查询的单词
shared_ptr<set<TextQuery::line_no>> lines; // 指向出现的行号所集成的set的智能指针
shared_ptr<vector<string>> file; // 输入文件,此处用的是智能指针,不会造成复制等耗费时间的事情
};
ostream& print(ostream& os, const QueryResult& qr) // qr保存的是那个单词,以及指向保存文本文件的vector<string>的指针
{
os << qr.sought << " occurs " << (*(qr.lines)).size() << " times" << endl; // 后面那个是set的size()
for (const auto& num : *(qr.lines))
{
os << "\t(lines " << num + 1 << ") " << (*(qr.file))[num]/**(qr.file->begin() + num)*/ << endl;
}
return os;
}
void runQueries(ifstream &infile)
{
// infile是一个ifstream,指向我们要处理的文件
TextQuery tq(infile); // 保存文件并建立查询map
// 与用户交互,提示用户输入要查询的单词,完成查询并打印结果
while (true)
{
string word;
cout << "enter word to look for, or q to quit";
// 若遇到文件尾或用户输入了'q'时循环终止
if (!(cin >> word) || word == "q")
break;
// 指向查询并打印结果
// 每输入一个单词,就调用这个保存好输入的文件的TextQuery类的对象的成员函数query去查询这个单词,返回值是一个QueryResult类的对象
print(cout, tq.query(word)) << endl;
}
}
略去了主函数,由于第八章没学好,所以有关文件的读取,以及其中神奇的sstringstream也没能理解。这个程序对于理解智能指针shared_ptr<>类以及map,set类,以及没有掌握的文件读取都是一个很好的练习和巩固。
其实程序的一些细节算法并不难,主要是怎么选择合适的数据结构来处理这个文本文件,这两个类的结合,TextQuery类只有一个构造函数,参数是ifstream&,这个类的作用就是把这个文件里的文本处理之后,存储在shared_ptr<vector<string>>智能指针以及map<string, shared_ptr<set<size_type>>>中,这个map存储每个单词出现的行号,每个string对应一个shared_ptr<set<size_type>>智能指针,而唯一的实用查询成员函数,query就是来接收一个string,然后返回一个合适的智能指针,如果这个string(单词)存在,则返回map中string配对的那个智能指针,指向set保存行号,如果不存在,返回的智能指针即函数内创建的指向空set的智能指针,而这些shared_ptr并不是直接返回的,而是把这个shared_ptr和单词,以及文本封装在一个QueryResult类的对象中,这个对象就是它的名字所说的:查询结果。即查询的单词,以及查询的文本,以及查询结果,这个结果就是一个指向一个set的shared_ptr,保存的是查询的文本中单词出现的行号,这里用shared_ptr就避免了拷贝set和vector所带来的性能损耗。
|