#include "stdafx.h"
#include<iostream>
#include<string>
#include<vector>
#include<memory>
#include<algorithm>
#include<fstream>
#include<map>
#include<set>
#include<sstream>
#include<iterator>
using namespace std;
class QueryResult;//保存查询结果的类
class TextQuery//保存待查询文本,并对文本中各单词与其出现行号的集合的指针构建map
{
public:
using line_no = vector<string>::size_type;
TextQuery(ifstream &);//从文件中读取文本构造TextQuery类
QueryResult query(const string &) const;//返回对一个单词的查询结果,有点类似WordQuery
private:
shared_ptr<vector<string>> file;//动态分配内存保存文本内容,每行当做一个string放进vector(file)中
map<string, shared_ptr<set<line_no>>> wm;//注意这里的map是文本中各单词与其(出现行号的集合)的指针之间的映射
};
TextQuery::TextQuery(ifstream &is):file(new vector<string>)//并没有在初始化列表里一步到位进行初始化,具体的初始化细节是在函数体里实现的
{
string text;
while (getline(is, text))//读取文本中的每一行内容
{
file->push_back(text);//对file进行初始化
int n = file->size() - 1;//记录当前行序号
istringstream line(text);//将istringstream绑定在text上,便于对其中各单词进行操作,例子见P288
string word;
while (line >> word)//提取每行中的每个单词
{
//之所以加引用是因为lines是指向set的shared_ptr
auto &lines = wm[word];//利用map的特性,单词第一次出现会自动创建一个关键字,省去了麻烦的判断
if (!lines)//如果单词第一次出现lines将会是一个空指针,可我们并不想让它是个空指针,我们只是想让它指向一个空的set
lines.reset(new set<line_no>);
lines->insert(n);//将word当前所在的行号放进set里
}
}
}
class QueryResult
{
friend ostream &print(ostream &, const QueryResult &);//非成员函数的类接口
public:
using line_no = TextQuery::line_no;
QueryResult() = default;//默认构函,虽然在实际使用中会出错,但还是显式定义了一个
QueryResult(string s,shared_ptr<set<line_no>> p,shared_ptr<vector<string>> f):sought(s),lines(p),file(f){}
auto begin() const
{
return lines->begin();
}
auto end() const
{
return lines->end();
}
shared_ptr<vector<string>> get_file() const
{
return file;
}
private:
string sought;//查询的内容(准确来说是一个式子)
shared_ptr<set<line_no>> lines;//单词在文本中出现的行号
shared_ptr<vector<string>> file;//文本内容,用于在print中进行输出,一行一个string
};
QueryResult TextQuery::query(const string &sought) const
{
static shared_ptr<set<line_no>> nodata(new set<line_no>);//假如找不到单词,就返回一个指向空内容内存的指针
auto loc = wm.find(sought);//loc是指向pair的迭代器
if (loc == wm.end())//没找到
return QueryResult(sought, nodata, file);
else//找到了
return QueryResult(sought, loc->second, file);//这里不能用wm[sought],因为query是一个常量成员函数,而map的下表操作默认会改变map
}
ostream &print(ostream &os, const QueryResult &qr)//之所以用输出流作为参数和返回类型是便于不同的输出操作,比如可以标准输出也可以输出到文件里
{
os << qr.sought << " occurs " << qr.lines->size() << " " << "time(s)" << endl;
for (auto n : *qr.lines)//n是line_no
{
os <<"(line_no:"<<n+1<<")"<< " " << (*qr.file)[n] << endl;
}
return os;
}
class Query_base//抽象基类
{
friend class Query;
protected:
using line_no = TextQuery::line_no;
virtual ~Query_base() {}
private://将两个接口(纯虚)函数放在private里,是不想用户(派生类)直接使用Query_base,后面接口类将两个函数放在public里就行了,毕竟各种query实际都是Query类的对象
virtual QueryResult eval(const TextQuery&) const = 0;//实际上求返回结果就是求了个QueryResult
virtual string rep() const= 0;
};
class Query//接口类,用于隐藏Query_base的继承体系
{
//这些运算符都用到了从指针创建Query对象的构造函数
friend Query operator~(const Query &);
friend Query operator|(const Query &, const Query &);
friend Query operator&(const Query &, const Query &);
public:
Query(const string&);//与其他类型的query不同,WordQuery可以直接由Query类构造得到
QueryResult eval(const TextQuery &t) const
{
return q->eval(t);//为什么在Query_base里两个函数是private的,在这儿却能从外部调用?
//因为Query是Query_base的友元类,用q调用eval/rep函数都是在静态类型中进行名字查找的,所以Query_base的
//各派生类并不需要对Query类进行友元声明,它们只进行动态绑定
}
string rep() const { return q->rep(); }
private:
Query(shared_ptr<Query_base> query) :q(query){}//用于各个运算符的定义,但又不想外部访问,所以各运算符声明为了友元函数
shared_ptr<Query_base> q;//各种query实际存储在这里
};
ostream &operator<<(ostream &os, const Query &query)//不能定义在类Query里(定义在类内改变某类对象的该运算符操作,定义在类外则会改变所有对象的该运算符操作)
{
return os << query.rep();//因为rep在基类里是虚函数,所以这里的调用会执行动态绑定
}
class WordQuery :public Query_base
{
friend class Query;//Query类用到了WordQuery的构造函数,而友元关系不能继承,只能再次声明
WordQuery(const string &s) :query_word(s){}
QueryResult eval(const TextQuery &t) const
{
return t.query(query_word);//WordQuery的查询可以直接由TextQuery实现
}
string rep() const
{
return query_word;//有点怪怪的,根据之前的输出运算符,难道WordQuery就输出一个查询单词???
//rep函数只是将输入的内容转换为标准的运算式形式,并不是用来输出查询结果的,应该是用QueryResult的print函数来输出查询结果
}
string query_word;
};
inline Query::Query(const string&s) :q(new WordQuery(s)){}//new返回的普通指针可以直接用来初始化智能指针
class NotQuery :public Query_base
{
//不需要对Query类进行友元声明
friend Query operator~(const Query&);//~运算符使用了NotQuery的构造函数
NotQuery(const Query &q) :query(q){}//这个构造函数并无任何问题,在NotQuery里存着一个要取反的Query而已
//对两个接口函数进行覆盖
QueryResult eval(const TextQuery&) const;//其实真正区分各种Query的是eval函数
string rep() const override
{
return "~(" + query.rep() + ")";
}
Query query;//要对哪个Query取反
};
inline Query operator~(const Query &q)
{
return shared_ptr<Query_base>(new NotQuery(q));
//就是在这种情况用到了从指针到Query对象的转换,需要Query的构造函数
//还使用了NotQuery类的构造函数
}
class BinaryQuery :public Query_base//抽象基类
{
protected://下面这些成员都要在派生类中使用
BinaryQuery(const Query &q1,const Query &q2,string os):lhs(q1),rhs(q2),opSym(os){}
//不定义eval函数,直接继承一个纯虚函数,反正自己也是一个抽象基类
string rep() const override
{
return "("+lhs.rep() + " "+opSym+" " + rhs.rep()+")";
}
Query lhs;
Query rhs;
string opSym;//运算符
};
class AndQuery :public BinaryQuery
{
friend Query operator&(const Query &, const Query &);
AndQuery(const Query &q1,const Query &q2):BinaryQuery(q1,q2,"&"){}
QueryResult eval(const TextQuery &) const;
};
inline Query operator&(const Query &q1,const Query &q2)
{
return shared_ptr<Query_base>(new AndQuery(q1, q2));
}
class OrQuery :public BinaryQuery
{
friend Query operator|(const Query &, const Query &);
OrQuery(const Query &q1,const Query &q2):BinaryQuery(q1,q2,"|"){}
QueryResult eval(const TextQuery &) const;
};
inline Query operator|(const Query &q1, const Query &q2)
{
return shared_ptr<Query_base>(new OrQuery(q1, q2));
}
//eval实际就是求lines,lines是创建QueryResult的关键
QueryResult NotQuery::eval(const TextQuery &t) const
{
//先求得单词出现的行号
auto result = query.eval(t);
auto ret_lines = make_shared<set<line_no>>();
//对于输入文件中的每一行,如果没有出现在result中,则把它添加到ret_lines里
auto beg = result.begin();
auto end = result.end();
auto sz = result.get_file()->size();
for (size_t n = 0;n != sz;++n)//在文本中遍历
{
if (beg == end || n != *beg)//result中的遍历已经结束或者当前行号不在result中,只有file中的指针后移一位
//之所以能这么写,是因为result和file中的行号都是按顺序排列的
{
ret_lines->insert(n);
}
else if (beg != end)//当前行号在result当中且result还没遍历完,result和file中的指针都向后移动一位
{
++beg;
}
}
return QueryResult(rep(), ret_lines, result.get_file());
}
QueryResult OrQuery::eval(const TextQuery &t) const
{
//得到两个Query对象的QueryResult
auto right = rhs.eval(t);
auto left = lhs.eval(t);
//创建行号列表的指针
auto ret_lines = make_shared<set<line_no>>(left.begin(), left.end());
ret_lines->insert(right.begin(), right.end());//set会自动忽略重复的关键字
return QueryResult(rep(), ret_lines, left.get_file());
}
QueryResult AndQuery::eval(const TextQuery &t) const
{
auto left = lhs.eval(t);
auto right = rhs.eval(t);
auto ret_lines = make_shared<set<line_no>>();
//这个函数将两个查询结果的交集添加到ret_lines中
set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin()));//原来是在这里出了问题
return QueryResult(rep(), ret_lines, left.get_file());
}
int main()
{
ifstream ifs("d://TextQuery.txt");
TextQuery tq(ifs);//TextQuery会自动创建好file和map
/*Query q("Alice");*/
//事实上直接使用Query的构造函数的都是WordQuery,其它的Query其实都是由WordQuery通过运算符运算得到的
Query q = Query("Alice") | Query("wind");
QueryResult qr=q.eval(tq);
print(cout, qr);
return 0;
}
C++Primer5th 文本查询程序再探
最新推荐文章于 2022-06-18 14:57:45 发布