IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> C++ Primer - TextQuery设计 -> 正文阅读

[数据结构与算法]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类的使用,从而打印我们所希望看到的结果。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-12 16:37:25  更:2022-05-12 16:38:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:34:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码