すのはら荘春原庄的雪

C++文本查询再探

Toretto·2022-04-18 09:21·137 次阅读

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; }
复制代码

 

posted @   晓风霜度-  阅读(137)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示