C++ Primer - TextQuery设计

        在C++ Primer书中设计了TextQuery的程序,该程序可以实现从本地加载文章,并记录每个单词位于第几行。当用户输入单词,程序就可以返回所有的包含该单词的一行文字。实验结果如下图所示:

        因此,我们可以简单分析一下,对于TextQuery类而言,它涉及到哪些数据结构?

(1)需要打印某一行的文字,因此我们可以使用vector将每一行的文字都进行保存。

(2)当用户输入某个单词时,程序就会打印包含该单词的所有行。因此,就涉及到map数据结果。键是单词,而值则是该单词在哪些行中出现了。而值则使用set进行表示,使得某一行出现多次的一个单词,仅仅打印一次该段的单词。

(3)为了分割一段文字中所有的单词,我们可以使用istringstream类,该类可以根据空格对一段文字进行分割。

        接着,我们需要分析一下TextQuery涉及到哪些成员方法,从而支持上述我们的需求呢?

(1)需要提供读取文件的方法:这个方法可以继承在TextQuery中构造函数中,一旦构造了TextQuery的实例对象之后,就已经实现了对某个文件的读取与加载。

(2)需要提供查询的方法:该方法支持我们输入单词,并输出包含该单词的所有行(重复的行文字仅仅打印一次)。

(3)打印最终的查询结果:我们可以再创建一个成员方法,实现对查询结果的打印。但是C++ Primer提供了一个耦合性更低的思路,额外创建一个QueryResult类的方法来代替在TextQuery中创建一个成员方法的思路。我们将最终的查询结果封装在QueryResult类中,使用其友元函数来打印最终的效果。

1. testQuery.hpp程序

#ifndef __TEXTQUERY_H__
#define __TEXTQUERY_H__

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

// 对QueryResult类进行声明,因为TextQuery类的query函数需要返回QueryResult对象
class QueryResult;
class TextQuery{
private:
    using data_type = std::vector<std::string>::size_type;
    std::shared_ptr<std::vector<std::string>> content;
    std::map<std::string, std::shared_ptr<std::set<data_type>>> record;
public:
    TextQuery(std::ifstream& is);
    QueryResult query(const std::string& word);
    ~TextQuery(){};
};
# endif

        如上述代码所示,我们首先声明了QueryResult类,这样在TextQuery类中就可以显示的定义query查询方法了。上述代码有几点是需要着重注意的:

(1)使用vector<string>存放的每行文字,使用shared_ptr进行封装了;set<data_type>存在某个单词在哪些行出现,也是用shared_ptr进行封装了。一方面这些数据都需要在QueryResult中进行存在,shared_ptr表示这些数据可以在两个类之间进行共享;另一方面,使用智能指针可以省去我们对指针进行分配与销毁的精力。

(2)using关键字可以代替typedef对数据类型进行重命名,使用size_type而不是显示定义set的数据类型,因为在实际过程中,文件可大可小,需要根据具体的文件进行分析,因此我们不能在一开始就定死了set中数据的类型。

2. testQuery.cpp程序

#include <sstream>
#include <fstream>
#include "textQuery.hpp"
#include "queryResult.hpp"

TextQuery::TextQuery(std::ifstream& is):content(new std::vector<std::string>){
    // 读取文件,将每一行数据保存到content中,然后再将每行中的word保存到record词典中
    std::string line;
    while(std::getline(is, line)){
        int line_num = content->size();
        content->emplace_back(line);
        std::istringstream iss(line);
        std::string word;
        while(iss >> word){
            auto& line_set = record[word];
            if(!line_set){
                // line_set = std::make_shared<std::set<data_type>>(new std::set<data_type>);
                line_set.reset(new std::set<data_type>);
            }
            line_set->insert(line_num);
        }
    }
}

// 查询函数
QueryResult TextQuery::query(const std::string& word){
    auto pos = record.find(word);
    static std::shared_ptr<std::set<data_type>> nodata;
    if(pos == record.end()){
        return QueryResult(word, nodata, content);
    }else{
        return QueryResult(word, pos->second, content);
    }
        
}

        上述代码是TextQuery类具体的实现代码,其中有几点需要特别注意:

(1)TextQuery并没有文件地址的属性,而是使用ifstream来代替文件地址。

(2)使用getline()函数进行每一行数据的读取、使用istringstream类对一行数据中多个单词进行分割。

(3)map的插入也是一个非常有技巧的点,我刚开始想的是使用count()方法来判断某一个次是否在之前就出现了。如果出现了,直接插入在其对应的set中即可;如果没出现,就需要我们创建set指针。但是随着文章数据的增多,count()方法的负担会越来越大,这会极大的降低程序的效率。因此,C++ Primer中首先索引出某个单词对应的set智能指针,如果智能指针为空,则我们需要创建智能指针(使用智能指针的reset方法);如果不为空,我们直接在智能指针中插入数据(使用智能指针的insert方法)。更重要的一点是,这个智能指针必须是引用,否则将不会对set产生变换。

(4)在query中,我们创建了一个局部静态变量的智能指针来表示“文中不存在该单词”的情况。对于word单词的查询,我们可以使用map的find()方法,如果返回的位置已经位于map的尾部了,说明文中不存在该单词,并构造一个包含空的set的QueryResult类;否则就构建包含该单词的所有行号的QueryResult类。

3. queryResult.hpp程序

#ifndef __QUERYRESULT__H_
#define __QUERYRESULT__H_
#include <iostream>
#include <string>
#include <set>
#include <map>
#include <vector>
#include <memory>

class QueryResult{
private:
    using data_type = std::vector<std::string>::size_type;
    const std::string word;
    std::shared_ptr<std::vector<std::string>> content;
    std::shared_ptr<std::set<data_type>> line_set;

public:
    QueryResult(const std::string& w, std::shared_ptr<std::set<data_type>> ls, 
        std::shared_ptr<std::vector<std::string>> c):word(w),line_set(ls),content(c){};
    ~QueryResult(){};
    // 定义一个友元函数即可,用于打印查询的结果
    friend std::ostream& show_result(std::ostream& os, const QueryResult& qr);


};

#endif // __QUERYRESULT__H_

        QueryResult类主要是对查询的结果进行打印的。因此,show_result函数则并不需要是QueryResult类的成员方法,只需要该类的友元方法即可,并对QueryResult类的属性记性打印。

4. queryResult.cpp程序

#include "queryResult.hpp"


std::ostream& show_result(std::ostream& os, const QueryResult& qr){
    // 用于打印最终的结果,如果qr中的line_set为空的话,说明没找到相应的元素
    if(!qr.line_set){
        os << "该文中没有" << qr.word << "关键字" << std::endl;
        return os;
    }
    os << "总共有" << qr.line_set->size() << "行出现了" << qr.word << "单词:"<< std::endl;
    for(auto ln: *qr.line_set){
        os << "(line " << ln << "): " << (*qr.content)[ln] << std::endl;
    }
    return os;
}

        在show_result()方法中,我们主要用于对ostream类的使用,从而打印我们所希望看到的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值