C++文本查询再探
最终版文本查询,功能十分全面的一份代码。出栈入栈那块是C风格,不想看可以不看。
#include <vector> #include <map> #include <set> #include <memory> #include <string> #include <fstream> #include <ostream> #include <iostream> #include <sstream> #include <algorithm> // set_intersection #include <iterator> // insert_iterator #include <stack> using namespace std; using line_no = vector<string>::size_type;//定义一个类型成员来引用行号 //=============数据类============== class QueryResult { //用来打印搜索结果 friend ostream& print(ostream& os, const QueryResult& qr); public: QueryResult(const string& word,shared_ptr<set<line_no>> wns, shared_ptr<vector<string>> lines) :_word(word) ,_lineNums(wns) ,_lines(lines) {} //返回所有按行存储的_lines shared_ptr<vector<string>> get_lines() { return _lines; } //返回搜索结果的总行数 line_no linesSize() { return _lines->size(); } //返回_lineNums.begin() auto begin() { return _lineNums->begin(); } //返回_lineNums.end() auto end() { return _lineNums->end(); } private: string _word;//要查询的单词 shared_ptr<set<line_no>> _lineNums;//单词出现的行号 shared_ptr<vector<string>> _lines; //文件的文本内容,按行保存 }; //存放文本内容(按行保存) class TextQuery { public: TextQuery(ifstream& ifs)//构造函数,打开文件,获取文件的内容 :_lines(new vector<string>)//指针才需要初始化吧?容器map不需要 { //打开文件,读取文件内容,并保存在数据成员中 string line; size_t no = 0; string word; while (getline(ifs, line)) { //按行提取文本数据,并拆分成单词 _lines->push_back(line);//保存新的一行 //拆分单词 istringstream iss(line); while (iss >> word) { auto& wns = _word2Nums[word]; if(!wns) { wns.reset(new set<line_no>); } wns->insert(no); } ++no;//行号加一 } } QueryResult query(const string& word) const { //返回QueryResult类型的对象,实际在这里进行对象的创建 //由于有可能要查找的单词不在文本中,所以需要定义一个空的set static shared_ptr<set<line_no>> nodata(new set<line_no>);//空的set auto loc = _word2Nums.find(word);//返回的是迭代器 if (loc == _word2Nums.end()) { //没有找到,传入一个空set //由于类型都是shared_ptr,所以传递过程中属于浅拷贝 return QueryResult(word, nodata, _lines); } else { //找到了,传入行号的set,和文本内容 return QueryResult(word, loc->second, _lines); } } private: //由于QueryResult对象需要用到TextQuery对象中的数据成员 //避免在使用之前被销毁,将需要共享出去的成员定义为shared_ptr类型 shared_ptr<vector<string>> _lines;//保存文本每一行的内容 map<string,shared_ptr<set<line_no>>> _word2Nums;//保存单词出现的行号,从0开始 }; ostream& print(ostream& os, const QueryResult& qr) { //打印搜索结果 os << "\"" << qr._word << "\" " << "occurs " << qr._lineNums->size() << " times." << endl; for(auto &no:*qr._lineNums) { os << "\t(line " << no + 1 << ") " << *(qr._lines->begin() + no) << endl; } return os; } //================更加丰富的查询方法================= class Query; //声明 //公共抽象基类Query_base class Query_base { friend class Query; protected: Query_base() {} //基类中有虚函数,需要虚析构函数,显式设置为默认函数体 //虚析构函数是为了解决父类指针指向子类对象时,释放子类对象的资源时, //释放不完全,造成的内存泄漏问题。 virtual ~Query_base() = default; private: //虚函数eval,使用给定的TextQuery对象查找与之匹配的行,并返回结果 virtual QueryResult eval(const TextQuery& tq) const = 0; //虚函数rep,返回实际要查询的单词 virtual string rep() const = 0; }; // class WordQuery; //============= WordQuery ============ //查找给定的queryWord,实际在TextQuery对象上进行查询操作的唯一一个类 class WordQuery :public Query_base { friend class Query;//Query的构造函数会调用该类的构造函数,需要设为友元类 WordQuery(const string& queryWord)//构造函数 :_queryWord(queryWord) {} //覆盖eval和rep QueryResult eval(const TextQuery& tq) const override { return tq.query(_queryWord); } string rep() const override { return _queryWord; } string _queryWord;//实际要查询的单词 }; //供用户使用的接口类,隐藏了Query_base的继承体系 class Query { //重载逻辑运算符,生成最终的查询语句 friend Query operator~(const Query& rhs); friend Query operator|(const Query& lhs, const Query& rhs); friend Query operator&(const Query& lhs, const Query& rhs); public: Query(const string& word) //生成WordQuery类型的对象,赋值给_query :_query(new WordQuery(word)) {} QueryResult eval(const TextQuery& tq) const//调用接口 { return _query->eval(tq);//虚调用,动态链编 } string rep() const//调用接口 { return _query->rep();//虚调用 } private: Query(shared_ptr<Query_base> query)//构造函数 : _query(query) {} shared_ptr<Query_base> _query; //用来动态决定调用那个版本的函数 }; ostream& operator<<(ostream& os, const Query& query) { //重载输出运算符,用来打印Query::rep()的返回值 return os << query.rep(); } //============= NotQuery ============ class NotQuery :public Query_base { friend Query operator~(const Query& rhs); NotQuery(const Query& q)//构造函数 :_query(q) {} //覆盖eval和rep QueryResult eval(const TextQuery& tq) const override { //此处因为构造函数的缘故,_query已经过拷贝构造函数 //Query q=Query("and"); 是WordQuery,所以hasRes=return tq.query(_queryWord); QueryResult hasRes = _query.eval(tq);//包含queryWord的行 //存放最终不包含queryWord的行的set,初始为空,make_shared是初始化函数 shared_ptr<set<line_no>> retLines = make_shared<set<line_no>>(); //遍历所有行,和hasRes对比,不在hasRes的行就是符合条件的,存到retLines中 auto beg = hasRes.begin(); auto end = hasRes.end(); auto sz = hasRes.linesSize();//它返回的就是line_no类型 //单词所在行号的集合set的begin与end //linesSize是文本总行数,sz是文本总行数 //通过beg指针++来遍历 for (line_no i = 0; i < sz; ++i) { if (beg == end || *beg != i) { //当前遍历到的行,不在hasRes中,保存到retLines中 retLines->insert(i); } else if (beg != end) { //当前遍历到的在hasRes中,继续获取下一行(如果存在) ++beg; } } //get_lines必须QueryResult类对象才能调用,它与TextQyery类共用 return QueryResult(rep(), retLines, hasRes.get_lines()); } string rep() const override { return "~(" + _query.rep() + ")"; } Query _query; }; //通过重载~Query来调用NotQuery,但本质上返回值还是Query
//这里return发生了隐式转换,Query有个构造函数里面参数就是Query_base指针
Query operator~(const Query& rhs) { return shared_ptr<Query_base>(new NotQuery(rhs)); } //==============与或查询派生类============= //与或查询的抽象基类,保存运算符左右两个运算对象 class BinaryQuery :public Query_base { protected: BinaryQuery(const Query& lhs, const Query& rhs, const string& op) :_lhs(lhs) , _rhs(rhs) , _opSym(op) {} //这个类不用覆盖eval string rep() const override { //因为都是string类型所以可以直接+ return "(" + _lhs.rep() + " " + _opSym + " " + _rhs.rep() + ")"; } protected: //派生类需要使用下面的数据成员,所以设置为protected Query _lhs; Query _rhs; string _opSym;//保存运算符 "|"、"&" }; //BinaryQuery主要提供rep的保存工作,并提供数据成员,其实这里不加这个BinaryQuery也可以, //只是BinaryQuery这种写法更对应面向对象的编程思想//这点很重要,要与C语言划清界限 //实际取交集返回QueryResult还得是AndQuery自己 //与运算 class AndQuery :public BinaryQuery { friend Query operator&(const Query& lhs, const Query& rhs); private: AndQuery(const Query& lhs, const Query& rhs)//构造函数 :BinaryQuery(lhs,rhs,"&") {} //覆盖eval,rep直接使用从BinaryQuery基类继承过来的即可 QueryResult eval(const TextQuery& tq) const override { //取两个集合的交集 QueryResult lRet = _lhs.eval(tq); QueryResult rRet = _rhs.eval(tq); //要动态分配哦 shared_ptr<set<line_no>> retLines = make_shared<set<line_no>>(); //取交集,调用标准库算法 set_intersection 来合并两个集合 set_intersection(lRet.begin(), lRet.end(),rRet.begin(), rRet.end(), inserter(*retLines, retLines->begin())); return QueryResult(rep(), retLines, lRet.get_lines()); } }; Query operator&(const Query& lhs, const Query& rhs) { return shared_ptr<Query_base>(new AndQuery(lhs, rhs));//递归 } //或运算 class OrQuery :public BinaryQuery { friend Query operator|(const Query& lhs, const Query& rhs); private: OrQuery(const Query& lhs, const Query& rhs)//构造函数 :BinaryQuery(lhs, rhs, "|") {} //覆盖eval,rep直接使用从基类BinaryQuery继承过来的即可 QueryResult eval(const TextQuery& tq) const override { //返回两个集合的并集 QueryResult lRet = _lhs.eval(tq);//继承的BinaryQuery的 _lhs与_rhs QueryResult rRet = _rhs.eval(tq); //取两个集合的并集 shared_ptr<set<line_no>> retLines = make_shared<set<line_no>>(lRet.begin(), lRet.end()); retLines->insert(rRet.begin(), rRet.end()); return QueryResult(rep(), retLines, lRet.get_lines()); } }; Query operator|(const Query& lhs, const Query& rhs) { return shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } /***************************test************************************/ //这里的入栈与出栈的代码属于C语言风格,考验C基本功的时候到了... bool str2Query(const string str, Query& query) { //把用户输入的字符串变换成Query类型 stack<char> st4op; //用来解析表达式的栈,存储运算符 stack<Query> st4Query;//存储单词的栈 string word; //遍历str,借助两个栈来解析str for (auto elem : str) { if (isalpha(elem)) { word += elem;//string是可以加字符的 } else if (elem == '~') { if (word.size()) {//不为0说明左边有字母存在 //word1 ~ word2, 非法表达式,直接返回 cout << "'~'wrong expression!" << endl; return false; } st4op.push(elem); } else if (elem == '(') { //左括号,把'('压栈 st4op.push(elem); } else if (elem == '&') { if (word.size()) { //与运算,如果前一个是单词,压栈,并清空单词 query = Query(word);//动态调用 st4Query.push(query); word.clear(); if (st4op.empty()) { //word1 & word2 st4op.push(elem); continue; } if (st4op.top() == '&') { //如果是word1 & word2 & word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { //如果是word1 | word2 & word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } } //如果前面什么都没有,非法表达式,直接返回 else if (st4Query.empty()) { cout << "wrong exppression!" << endl; return false; } //如果前面是表达式 ( ... ) else if (st4op.top() == '&') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } st4op.push(elem); } else if (elem == '|') { if (word.size()) { //或运算,如果前一个是单词,压栈,并清空单词 query = Query(word); st4Query.push(query); word.clear(); if (st4op.empty()) { //word1 | word2 st4op.push(elem); continue; } if (st4op.top() == '&') { //如果是word1 & word2 | word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { //如果是word1 | word2 | word3 st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } } //如果前面什么都没有,非法表达式,直接返回 else if (st4Query.empty()) { cout << "wrong exppression!" << endl; return false; } //如果前面是表达式 ( ... ) else if (st4op.top() == '&') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } st4op.push(elem); } else if (elem == ')') { //如果遇到右括号,弹栈 if (word.size()) { st4Query.push(Query(word)); word.clear(); } if (st4Query.empty()) { //非法表达式,直接返回 cout << "wrong exppression!" << endl; return false; } else { bool flag = false; while (!st4op.empty()) { //求出括号中的表达式 if (st4op.top() == '(') { //正确匹配了括号 flag = true; st4op.pop(); /* st4Query.push(query); */ if (st4op.top() == '~') { //~( ... ) st4op.pop(); query = ~st4Query.top(); st4Query.pop(); st4Query.push(query); } break; } else if (st4op.top() == '&') { //(word1 & word2) st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '~') { // (~word) st4op.pop(); query = ~st4Query.top(); st4Query.pop(); st4Query.push(query); } } if (word.size()) { //(word) query = Query(word); st4Query.push(query); flag = true; word.clear(); } if (!flag) { //如果括号无法正确匹配,表达式有错,直接返回 cout << "wrong exppression!" << endl; return false; } } } }//end of for if (word.size()) { //表达式不带括号的情况 query = Query(word); st4Query.push(query); } while (!st4op.empty() && !st4Query.empty()) { //~word的情况 if (st4Query.empty()) { //非法表达式 cout << "wrong expression!" << endl; return false; } if (st4op.top() == '&') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() & query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '|') { st4op.pop(); query = st4Query.top(); st4Query.pop(); query = st4Query.top() | query; st4Query.pop(); st4Query.push(query); } else if (st4op.top() == '~') { st4op.pop(); query = ~st4Query.top(); st4Query.pop(); } } //能走到这里的说明程序执行没问题 return true; } void test(ifstream& ifs) { TextQuery tq(ifs); bool flag = false; while (true) { cout << "enter expression to look for, or q to quit: "; string str;//用户输入的表达式 Query q(""); //用来调用接口 if (!getline(cin, str) || str == "q") { break; } //输入了表达式 cin.clear(); flag = str2Query(str, q); if (!flag) { //输入的表达式不正确 cout << "continue..." << endl; continue; } //输入了正确的表达式 print(cout, q.eval(tq)) << endl; } } int main() { ifstream ifs; ifs.open("china-daily.txt"); test(ifs); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具