すのはら荘春原庄的雪

文本查询项目--面向对象编程

Toretto·2022-04-15 14:01·44 次阅读

文本查询项目--面向对象编程

抛弃c语言面向过程的写法,使用C++面向对象的思路,充分发挥封装继承多态。

Query_base是抽象基类,提供接口函数用,声明纯虚函数。

Query为一个查询类,它的作用的隐藏Query_base,它有一个只能指针指向Query_base,Query与Query_base是has_a的关系(继承则是is_a的关系)。

复制代码
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
    QueryResult eval(const TextQuery& tq) const;//调用接口
    string rep() const;//调用接口

private:
    Query(shared_ptr<Query_base> query);//构造函数,当要查询的就是一个单词时
    /* ~Query(); */
    shared_ptr<Query_base> _query;  //用来动态决定调用那个版本的函数 与Query_base是has_a的关系
};
复制代码

TextQuery与QueryResult都属于数据结构类,用于存放数据。

TextQuery用于存放文档,通过函数查询指定单词来返回一个QueryResult结果。

这两个类之间数据成员需要共享,所以使用shared_ptr智能指针。

TextQuery有两个数据成员,_lines用于保存文本的每一行的内容,map用于存放单词极其对应的行号,因为行号可以是多个,所以用容器set保存,可以起到自动排序的作用。

在这里为什么要using line_no = vector<string>::size_type?因为容器的元素个数可以大于int型的表示范围,所以使用size_type,而size_type在系统函数中是被size_t typedef定义的。

QueryResult作为Query的返回结果,需注意结果可能会是空值。

 

复制代码
class TextQuery
{
public:
    TextQuery(ifstream& ifs);//构造函数,打开文件,获取文件的内容
    QueryResult query(const string& word) const;//查询word,并返回查询结果

private:
    //由于QueryResult对象需要用到TextQuery对象中的数据成员
    //避免在使用之前被销毁,将需要共享出去的成员定义为shared_ptr类型
    shared_ptr<vector<string>> _lines;  //保存文本每一行的内容
    map<string, shared_ptr<set<line_no>>> _word2Nums;//保存单词出现的行号,从0开始
    //一个单词可以出现在多行,所以行号用set容器保存
};

class QueryResult { //用来打印搜索结果 friend ostream& print(ostream& os, const QueryResult& qr); public: //构造函数 QueryResult(const string& word,shared_ptr<set<line_no>> lineNums,shared_ptr<vector<string>> lines); shared_ptr<vector<string>> get_lines();//返回所有按行存储的文本 line_no linesSize(); //返回搜索结果的总行数 auto begin();//返回_lineNums.begin() auto end(); //返回_lineNums.end() private: string _word;//要查询的单词 shared_ptr<set<line_no>> _lineNums; //单词出现的行号 shared_ptr<vector<string>> _lines; //文件的文本内容,按行保存 };
复制代码

 

 

 

 //text_query.h

复制代码
#ifndef __TEXT_QUERY_H__
#define __TEXT_QUERY_H__

#include <vector>
#include <map>
#include <set>
#include <memory>
#include <string>
#include <fstream>
#include <ostream>

using std::vector;
using std::map;
using std::set;
using std::shared_ptr;
using std::string;
using std::ifstream;
using std::ostream;
using line_no = vector<string>::size_type;//定义一个类型成员来引用行号

//===============基础查询方法===================

class QueryResult;//为了定义函数query的返回类型,需要先声明

class TextQuery
{
public:
    TextQuery(ifstream& ifs);//构造函数,打开文件,获取文件的内容
    QueryResult query(const string& word) const;//查询word,并返回查询结果

private:
    //由于QueryResult对象需要用到TextQuery对象中的数据成员
    //避免在使用之前被销毁,将需要共享出去的成员定义为shared_ptr类型
    shared_ptr<vector<string>> _lines;  //保存文本每一行的内容
    map<string, shared_ptr<set<line_no>>> _word2Nums;//保存单词出现的行号,从0开始
    //一个单词可以出现在多行,所以行号用set容器保存
};

class QueryResult
{
    //用来打印搜索结果
    friend ostream& print(ostream& os, const QueryResult& qr);

public:
    //构造函数
    QueryResult(const string& word,shared_ptr<set<line_no>> lineNums,shared_ptr<vector<string>> lines);
    shared_ptr<vector<string>> get_lines();//返回所有按行存储的文本
    line_no linesSize();  //返回搜索结果的总行数
    auto begin();//返回_lineNums.begin()
    auto end();  //返回_lineNums.end()

private:
    string _word;//要查询的单词
    shared_ptr<set<line_no>> _lineNums;     //单词出现的行号
    shared_ptr<vector<string>> _lines;      //文件的文本内容,按行保存
};

//================更加丰富的查询方法=================

class Query;  //声明

//公共抽象基类Query_base
class Query_base
{
    friend class Query;
protected:
    Query_base();//构造函数
    virtual ~Query_base() = default;  //基类中有虚函数,需要虚析构函数,显式设置为默认函数体
    /* virtual ~Query_base();//虚析构函数 */

private:
    //虚函数eval,使用给定的TextQuery对象查找与之匹配的行,并返回结果
    virtual QueryResult eval(const TextQuery& tq) const = 0;
    //虚函数rep,返回实际要查询的单词
    virtual string rep() const = 0;
};

//供用户使用的接口类,隐藏了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
    QueryResult eval(const TextQuery& tq) const;//调用接口
    string rep() const;//调用接口

private:
    Query(shared_ptr<Query_base> query);//构造函数,当要查询的就是一个单词时
    /* ~Query(); */
    shared_ptr<Query_base> _query;  //用来动态决定调用那个版本的函数 与Query_base是has_a的关系
};

//查找给定的queryWord,实际在TextQuery对象上进行查询操作的唯一一个类
class WordQuery
    :public Query_base
{
    friend class Query;//Query的构造函数会调用该类的构造函数,需要设为友元类

private:
    WordQuery(const string& queryWord);//构造函数
    /* ~WordQuery();//析构函数 */

private:
    //覆盖eval和rep
    QueryResult eval(const TextQuery& tq) const override;
    string rep() const override;

    string _queryWord;//实际要查询的单词
};

//查询不包含某单词的类NotQuery
class NotQuery
    :public Query_base
{
    friend Query operator~(const Query& rhs);

private:
    NotQuery(const Query& q);//构造函数
    /* ~NotQuery();//重写虚析构函数 */

private:
    //覆盖eval和rep
    QueryResult eval(const TextQuery& tq) const override;
    string rep() const override;

    Query _query;
};

ostream& operator<<(ostream& os, const Query& query);

//==============与或查询派生类=============
//与或查询的抽象基类,保存运算符左右两个运算对象
class BinaryQuery
    :public Query_base
{
protected:
    BinaryQuery(const Query& lhs, const Query& rhs, const string& op);
    //这个类不用覆盖eval
    string rep() const override;

protected:
    //派生类需要使用下面的数据成员,所以设置为protected
    Query _lhs;
    Query _rhs;
    string _opSym;//保存运算符 "|"、"&"
};

//与运算
class AndQuery
    :public BinaryQuery
{
    friend Query operator&(const Query& lhs, const Query& rhs);

private:
    AndQuery(const Query& lhs, const Query& rhs);//构造函数

    //覆盖eval,rep直接使用从BinaryQuery基类继承过来的即可
    QueryResult eval(const TextQuery& tq) const override;
};

//或运算
class OrQuery
    :public BinaryQuery
{
    friend Query operator|(const Query& lhs, const Query& rhs);

private:
    OrQuery(const Query& lhs, const Query& rhs);//构造函数

    //覆盖eval,rep直接使用从基类BinaryQuery继承过来的即可
    QueryResult eval(const TextQuery& tq) const override;
};

#endif
复制代码

//text_query.cc

复制代码
#include "text_query.h"
#include <iostream>
#include <sstream>
#include <algorithm>   // set_intersection
#include <iterator>    // insert_iterator

using namespace std;

//TextQuery的构造函数
TextQuery::TextQuery(ifstream& ifs)
    :_lines(new vector<string>)
{
    //打开文件,读取文件内容,并保存在数据成员中
    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];//shared_ptr类型的引用
            if (!wns) {
                //如果是新单词,那么指针就是空指针,需要新建
                wns.reset(new set<line_no>);
            }
            wns->insert(no);//插入新行号,根据set的特性会去除重复的行号
        }
        ++no;//行号加一
    }
}

QueryResult TextQuery::query(const string& word) const {
    //返回QueryResult类型的对象,实际在这里进行对象的创建
    //由于有可能要查找的单词不在文本中,所以需要定义一个空的set
    static shared_ptr<set<line_no>> nodata(new set<line_no>);

    auto loc = _word2Nums.find(word);
    if (loc == _word2Nums.end()) {
        //没有找到,传入一个空set
        //由于类型都是shared_ptr,所以传递过程中属于浅拷贝
        return QueryResult(word, nodata, _lines); 
        //shared_ptr<vector<string>> _lines;  //保存文本每一行的内容
    }
    else { 
        //找到了,传入行号的set,和文本内容
        return QueryResult(word, loc->second, _lines); //loc->second是set容器
    }
}

QueryResult::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>> QueryResult::get_lines() {
    return _lines;
}

//返回搜索结果的总行数
line_no QueryResult::linesSize() {
    return _lines->size();
}

//返回_lineNums.begin()
auto QueryResult::begin() {
    return _lineNums->begin();
}

//返回_lineNums.end()
auto QueryResult::end() {
    return _lineNums->end();
}

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;
}

//================更加丰富的查询方法================
ostream& operator<<(ostream& os, const Query& query) {
    //重载输出运算符,用来打印Query::rep()的返回值
    return os << query.rep();
}

//========== Query_base =========
Query_base::Query_base() {
    /* cout << "Query_base()" << endl; */
}

//=========== Query ============
//为用户提供调用接口
Query::Query(const string& word)
    :_query(new WordQuery(word))
{
    /* cout << "Query(const string &)" << endl; */
}

Query::Query(shared_ptr<Query_base> query)
    : _query(query)
{
    /* cout << "Query(shared_ptr<Query_base> )" << endl; */
}

//提供给用户的调用接口
QueryResult Query::eval(const TextQuery& tq) const {
    return _query->eval(tq);//虚调用,动态链编
}
string Query::rep() const {
    return _query->rep();//虚调用
}

//============= WordQuery ============
//查找给定的queryWord,实际在TextQuery对象上进行查询操作的唯一一个类
WordQuery::WordQuery(const string& queryWord)
    :_queryWord(queryWord)
{
    /* cout << "WordQuery(const string &)" << endl; */
}

//覆盖eval和rep
QueryResult WordQuery::eval(const TextQuery& tq) const {
    //直接调用前面的即可
    return tq.query(_queryWord);
}

string WordQuery::rep() const {
    //直接返回要查询的单词即可
    return _queryWord;
}

//============ NotQuery ============
//查询不包含某个单词的情况
Query operator~(const Query& rhs) {
    return shared_ptr<Query_base>(new NotQuery(rhs));
}

NotQuery::NotQuery(const Query& q)
    :_query(q)
{}

QueryResult NotQuery::eval(const TextQuery& tq) const {
    //返回非的查询结果
    QueryResult hasRes = _query.eval(tq);//包含queryWord的行

    //存放最终不包含queryWord的行的set,初始为空
    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();
    for (line_no i = 0; i < sz; ++i) {
        if (beg == end || *beg != i) {
            //当前遍历到的行,不在hasRes中,保存到retLines中
            retLines->insert(i);
        }
        else if (beg != end) {
            //当前遍历到的在hasRes中,继续获取下一行(如果存在)
            ++beg;
        }
    }
    return QueryResult(rep(), retLines, hasRes.get_lines());
}

string NotQuery::rep() const {
    return "~(" + _query.rep() + ")";
}
//========end of NotQuery============

//========== BinaryQuery ============
//与或查询的抽象基类,保存运算符左右两个运算对象
BinaryQuery::BinaryQuery(const Query& lhs, const Query& rhs, const string& op)
    :_lhs(lhs)
    , _rhs(rhs)
    , _opSym(op)
{}

string BinaryQuery::rep() const {
    return "(" + _lhs.rep() + " " + _opSym + " " + _rhs.rep() + ")";
}

//=========== AndQuery ============
//与运算
Query operator&(const Query& lhs, const Query& rhs) {
    //重载"&"运算符
    return shared_ptr<Query_base>(new AndQuery(lhs, rhs));//递归
}

AndQuery::AndQuery(const Query& lhs, const Query& rhs)
    :BinaryQuery::BinaryQuery(lhs, rhs, "&")
{
    /* cout << endl << "AndQuery(const Query &, const Query &)" << endl << endl; */
}

QueryResult AndQuery::eval(const TextQuery& tq) const {
    //取两个集合的交集
    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());
}

//=========== OrQuery =============
//或运算
Query operator|(const Query& lhs, const Query& rhs) {
    return shared_ptr<Query_base>(new OrQuery(lhs, rhs));
}

OrQuery::OrQuery(const Query& lhs, const Query& rhs)
    :BinaryQuery(lhs, rhs, "|")
{}

QueryResult OrQuery::eval(const TextQuery& tq)const {
    //返回两个集合的并集
    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());
}
复制代码

//main.cc

此处将用户输入的string数据转为Query,这里是面向过程的写法。

复制代码
#include "text_query.h"
#include <iostream>
#include <stack>
using namespace std;

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 @   晓风霜度-  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示