C++项目:基于boost在线文档实现的搜索引擎(二)

  • 索引模块
  • 索引模块的描述
  • 正排索引与倒排索引的建立
    • 正排索引
    • 倒排索引
  • jieba分词,正排查找,倒排查找
    • jieba分词
    • 索引查找
  • 索引的测试

上一篇:C++项目:基于boost在线文档实现的搜索引擎(一)
下一篇:C++项目:基于boost在线文档实现的搜索引擎(三)
github: https://github.com/duchenlong/boost-search-engine

通过之前的预处理的过程,我们将boost在线文档都进行了分解,得到了每一个html在线文档分词后的结果(titleurlcontent

之后我们就需要将正文进行拆分,为正文的每一个关键字建立一个索引,方便我们之后的搜索过程,这里可以使用C++STL中的哈希表,也就是unordered_map

这里我们需要完成 倒排索引的建立倒排索引与正排索引进行搜索文本的查找

索引模块

对于索引模块,也就是我们需要构建倒排索引

也就是需要提取关键字对指定文本进行分词,这一过程叫做倒排索引。他的核心就是根据一个词,映射到这个词所属的文档中(哈希表)

  • 正排索引:根据文档id,得到文档的内容
  • 倒排索引:根据文档的内容,得到文档的id

为了对每一个倒排索引与正排索引的关键字进行描述,我们给他们各自封装一个结构体:

 /**  正排索引的存储结构体*  根据文档 id 定位到文档的内容 *  防止文档过多,直接使用64位的 int 来存储*/struct frontIdx{int64_t _docId;string  _title;string  _url;string  _content;};/**  倒排索引存储的结构体*  根据文本的关键字 定位到 所属的文档Id*  为了后面根据权值排序,再加一个关键字的权值*/struct backwardIdx{int64_t _docId;int     _weight;string  _word;};

索引模块的描述

我们的索引模块会有两次使用的地方:

  1. 第一次就是我们启动服务器的时候,自动对指定目录下的所有 html文档进行分词,建立索引
  2. 第二次就是我们进行查找的时候,这时进行分词的就是我们的搜索内容,并对这个内容进行分词,正排索引与倒排索引。

这其中会有一些公共的代码块,所以我们可以对索引的地方进行封装,构建一个类Index来进行这一模块的描述:

     class Index{public:Index();//查找正排索引const frontIdx* GetFrontIdx(const int64_t doc_id);//查倒排索引const vector<backwardIdx>* GetBackwardIdx(const string& key);// 建立倒排索引 与 正排索引bool Build(const string& input_path);// jieba分词 对语句进行分词void CutWord(const string& input,vector<string>* output);private://根据一行 预处理 解析的文件,得到一个正排索引的节点frontIdx* BuildForward(const string& line);//根据正排索引节点,构造倒排索引节点void BuildInverted(const frontIdx& doc_info);private://正排索引vector<frontIdx> forward_index;//倒排索引  哈希表unordered_map<string,vector<backwardIdx> > inverted_index;// jieba分词cppjieba::Jieba jieba;};

正排索引与倒排索引的建立

首先,我们在建立索引的时候,所传的参数是预处理中存储文档进行解析后数据的文件的路径,这个路径中,一行即是一组数据,他的排列为 title\3url\3content\n。


所以在建立索引之前,我们需要进入这个文件中,然后一行一行的将所有html文档中的数据都读取了

在得到一个文档解析的数据后,我们需要得到单独的 titleurlcontent。因为他们中间被我们用特殊的符号\3分割开,我们可以使用split函数进行分割,而C++的STL中并没有实现这个函数,就借助与Boost中的split函数来实现一些。

boost::split(type, select_list, boost::is_any_of(","), boost::token_compress_on);
  1. type类型是std::vectorstd::string,用于存放切割之后的字符串

  2. select_list:传入的字符串,可以为空。

  3. boost::is_any_of(","):设定切割符为,(逗号)

  4. boost::token_compress_on:将连续多个分隔符当一个,默认没有打开,当用的时候一般是要打开的。
    boost:: token_compress_off:不会压缩分割结果,连续的分隔符时会返回 ""字符串

因为可能存在有些html文档中没有标题的情况,所有我们采用boost:: token_compress_off风格来分割字符串,遇到没有标题或者正文时,会直接返回""空字符串。并且,split函数进行封装时,因为这是一个公共的代码,所以我们防止公共代码出,common文件

因为涉及到对文档进行编号的问题,所以应该建立正排索引(得到文档id),再根据正排索引的数据建立倒排索引。

    // 建立索引bool Index::Build(const string& input_path){//  按行读取 存放预处理中解析出来的数据的文件 cout<<input_path<<" build index begin "<<endl;std::ifstream file(input_path.c_str());if(file.is_open() == false){cout<<input_path<< " file open error " <<endl;return false;}string line;int idx = 0;static string progess("|/-\\");while(std::getline(file,line)){//  针对当前行数据,进行正排索引frontIdx* doc_info = BuildForward(line);if(doc_info == nullptr){cout<< " forward build error "<<endl;continue;}//根据正排索引的节点,构建倒排索引BuildInverted(*doc_info);// 打印部分构建结果 防止过多cout影响时间复杂度if(doc_info->_docId % 100 == 0){//cout<< doc_info->_docId << " sucessed "<<endl;//进度条cout<<"\r"<<progess[idx % 4]<< doc_info->_docId << " sucessed " <<std::flush;idx++;}}cout<<"index build sucessed "<<endl;file.close();return true;}

进度条显示结果



正排索引

//根据一行 预处理 解析的文件,得到一个正排索引的节点
frontIdx* BuildForward(const string& line);

正排索引的参数就是一个文档需要处理的一行数据,所以需要先进行分词,获取单独的 titleurlcontent,然后再操作

因为正排索引建立的时机就是服务器启动的同时,然后根据所有文档操作一下。所以一开始的文档id完全凭我们自己的取值,那何不简单一点,就不用哈希表了(因为哈希表存在冲突的问题),我们可以用一个vector数组,那么数组的下标即为文档id的时候,我们可以真正做到O(1)的查找复杂度

这样,我们文档Id的一开始的取值就是0,每次新增的文档Id即为当前数组的大小

    //根据一行 预处理 解析的文件,得到一个正排索引的节点,并插入到正排数组中frontIdx* Index::BuildForward(const string& line){// 对一行数据进行拆分 \3 为分割点,依次为 title url contentvector<string> nums;common::Util::Split(line,"\3",&nums);if(nums.size() != 3){cout<<" file num error "<< nums.size()<<endl;return nullptr;}frontIdx doc_info;doc_info._docId     = forward_index.size();doc_info._title     = nums[0];doc_info._url       = nums[1];doc_info._content   = nums[2];forward_index.push_back(std::move(doc_info)); return &forward_index.back();}

我们在正排索引中添加文档正文的时候,因为这里的文档正文可能非常多,并且这个变量也是一个临时变量,出了这个函数就被析构了,我们何不利用一些C++中的move与右值引用呢?把这个临时变量变成一个右值,直接使用vector中的右值拷贝,省事又省时。

那么作为返回值,我们需要返回这个新的文档的节点,这个时候不能直接返回&doc_info,因为我们已经通过move操作将doc_info变成nullptr了,再说这也是一个临时变量,不能作为地址去返回。

倒排索引

//根据正排索引节点,构造倒排索引节点
void BuildInverted(const frontIdx& doc_info);

这里我们需要做的就是分别对title和content进行关键字拆分(jieba分词),然后再分别统计分词后的关键字作为title和content出现的次数

    struct backwardIdx{int64_t _docId;int     _weight;string  _word;};

这时候,文档的id关键字_word我们就知道了,还需要我们定义一个计算权值的公式来,因为title的长度一般都远远少于content的长度,所以让title中关键字的出现次数的比重大一点

 _weight = 10 * _titleCnt + _contentCnt;(不存在特殊性,完全自定义)
    //根据正排索引节点,构造倒排索引节点void Index::BuildInverted(const frontIdx& doc_info){//统计关键字作为 标题 和正文的出现次数struct WordCnt {int _titleCnt;int _contentCnt;WordCnt():_titleCnt(0),_contentCnt(0){} };unordered_map<string,WordCnt> wordMap;//针对标题进行分词vector<string> titleWord;CutWord(doc_info._title,&titleWord);for(string word : titleWord){//全部转为小写boost::to_lower(word);wordMap[word]._titleCnt++;}//针对正文进行分词vector<string> contentWord;CutWord(doc_info._content,&contentWord);for(string word : contentWord){boost::to_lower(word);wordMap[word]._contentCnt++;}//统计结果,插入到倒排索引中for(const auto& word_pair : wordMap){backwardIdx backIdx;backIdx._docId  = doc_info._docId;//自定义 权值 = 10 * titleCnt + contentCntbackIdx._weight = 10 * word_pair.second._titleCnt + word_pair.second._contentCnt;backIdx._word   = word_pair.first;vector<backwardIdx>& back_vector = inverted_index[word_pair.first];back_vector.push_back(std::move(backIdx));}}

同理,在添加到倒排索引的数组中的时候,又是一个临时变量,那为了减少不必要的拷贝,我们还是使用move进行右值拷贝

jieba分词,正排查找,倒排查找

jieba分词

    //jieba分词词典的路径const char* const DICT_PATH = "../jieba_dict/jieba.dict.utf8";const char* const HMM_PATH = "../jieba_dict/hmm_model.utf8";const char* const USER_DICT_PATH = "../jieba_dict/user.dict.utf8";const char* const IDF_PATH = "../jieba_dict/idf.utf8";const char* const STOP_WORD_PATH = "../jieba_dict/stop_words.utf8";Index::Index():jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH){forward_index.clear();inverted_index.clear();}

jieba分词的使用,就是看了github上下载之后,大佬们写的测试程序,然后修改一下,做一个接口就可以了

    // jieba分词 对语句进行分词void Index::CutWord(const string& input,vector<string>* output){jieba.CutForSearch(input,*output);

索引查找

没有什么特殊的,没有找到就返回nullptr,找到了就返回找到数据的指针

   //查找正排索引const frontIdx* Index::GetFrontIdx(const int64_t doc_id){if(doc_id < 0 || doc_id >= forward_index.size()){return nullptr;}return &forward_index[doc_id];}//查倒排索引const vector<backwardIdx>* Index::GetBackwardIdx(const string& key){auto it = inverted_index.find(key);if(it == inverted_index.end()){return nullptr;}return &(it->second);}

索引的测试

#include "searcher.hpp"
#include <ostream>int main() {searcher::Index index;bool ret = index.Build("../data/tmp/raw_input.txt");if (!ret) {std::cout << "file error, create index error " << std::endl;return 1;}// 索引构建成功, 就调用索引中的相关函数. (查正排+查倒排)auto* inverted_list = index.GetBackwardIdx("filesystem");for (const auto& weight : *inverted_list) {std::cout << "doc_id:" << weight._docId << "weight:" << weight._weight << std::endl;auto* doc_info = index.GetFrontIdx(weight._docId);std::cout << "title:" << doc_info->_title << std::endl;std::cout << "url:" << doc_info->_url << std::endl;std::cout << "content:" << doc_info->_content << std::endl;std::cout << "================================================================" << std::endl;}return 0;
}



所搜索的关键字出现了三次,都是在正文中出现,权值为3

C++项目:基于boost在线文档实现的搜索引擎(二)相关推荐

  1. 项目1:基于Java API文档制作的搜索引擎

    目录 一.搜索引擎相关概念 1.1 认识搜索引擎 1.2 搜索引擎的本质 1.3 搜索的思路 1.3.1  暴力搜索 1.3.2  倒排索引 1.4 项目目标 二.实现思路和前期准备 2.1 项目模块 ...

  2. 十三种技术文档模板_在线文档,知多少?

    不知大家有没有在线编辑文档的习惯 在线编辑文档有许多好处 比如:多平台同步.协作编辑等 今天给大家推荐三款在线文档软件 -- 金山文档.腾讯文档.石墨文档 限于篇幅,这里仅介绍它们对应的 Window ...

  3. WPS本地镜像化在线文档操作以及样例

    一个客户项目有引进在线文档操作需求,让我这边做一个demo调研下,给我的对接文档里有相关方法的说明,照着对接即可.但在真正对接过程中还是踩过不少坑,这儿对之前的对接工作做个记录. 按照习惯先来一个效果 ...

  4. 快速扩展在线文档产品特性的9个开源项目

    在线文档,笔记应用近几年发展迅速,这些产品的内容创造与管理也不再仅仅局限于文字或者图片,诸如科学公式,多维表格,流程图,思维导图,电子表格等元素也逐步被引入文档内容中. 而这些元素的编辑与展示在各个特 ...

  5. python 知识管理系统_MrDoc: 基于Python开发的Markdown在线文档系统,适合作为个人和小型团队的文档、笔记和知识管理工具...

    MrDoc觅道文档 - 记录文档.汇聚思想 个人和小型团队的笔记.文档.知识管理私有化部署方案 简介 MrDoc 是基于Python开发的在线文档系统,适合作为个人和小型团队的文档.知识和笔记管理工具 ...

  6. django开源电子文档管理系统_基于 Python 开发的在线文档系统

    MarkDown 编辑,快速书写:类 Gitbook,简洁阅读:后台管理. 州的先生(zmister.com)自用并完全开源.基于 Python 编写的文档写作系统. MrDoc 拥有以下特点: 站点 ...

  7. java基于springboot+vue的在线文档管理系统 nodejs 前后端分离

    随着社会的发展,社会的各行各业都在利用信息化时代的优势.计算机的优势和普及使得各种信息系统的开发成为必需. 在线文档管理系统,主要的模块包括查看首页.个人中心.公告信息管理.部门信息管理.岗位管理.员 ...

  8. 基于MFC单文档的画图程序(小项目)

    基于MFC单文档的画图程序 前言 学习了一年多的MFC和Qt,对其基本用法已经熟练掌握,迫不及待的想实战一波,但是心急吃不了热豆腐,我还是踏踏实实的找一些既能复习以前知识的单子又不消耗我太多时间(最近 ...

  9. BookStack在线文档管理系统 v2.9

    介绍: BookStack是一个基于MinDoc,使用Beego开发的在线文档管理系统,功能类似Gitbook和看云. 在开发的过程中,增加和移除了一些东西,目前已经不兼容MinDoc了(毕竟数据表结 ...

最新文章

  1. IOS手机全屏长按识别二维码HTML代码
  2. 20171218-编程语言的介绍
  3. fedora8完美DNS教程
  4. [开源 .NET 跨平台 Crawler 数据采集 爬虫框架: DotnetSpider] [一] 初衷与架构设计
  5. 我是一位老师,讲课是我的乐趣,可是……
  6. Spark内核源码学习(暂未学完)
  7. 数学建模灵敏度分析_数学建模中的灵敏度分析,到底在分析什么?
  8. Spring 框架基础(06):Mvc架构模式简介,执行流程详解
  9. 30万条数据,搜索文本字段的各种方式对比
  10. deepin linux下解决Qt搜狗输入法无法输入中文
  11. unity button 通过事件改变物体颜色
  12. step文件查看软件_3D PDF文件转换为step
  13. java认证考试例题_2016年Java认证考试题(3)
  14. Windows7开机加速全攻略
  15. mongo异常无法启动-处理方法
  16. android移植大作游戏,这款steam移植的1GB大作,或许是今年最有氛围的悬疑游戏
  17. 计算机中的颗粒度(granularity)什么是颗粒度?
  18. [WDS] Disconnected!问题解决
  19. 手机淘宝客户端架构探索实践
  20. bodgeito通关教程

热门文章

  1. AMOLED Demura 烧录图像控制屏幕灯珠方式
  2. 使用firefox插件httperrequest,模拟发送及接收Json请求
  3. JD京东物流电子面单接口对接文档-快递鸟
  4. linux下Oracle服务的启动和关闭
  5. 面向对象思想设计原则及常见设计模式
  6. shell排序(C++)
  7. WKWebView高度自适应
  8. UML基本概念——动态视图
  9. python中的.find用法
  10. 强力光盘刻录工具(BurnAware)12.4中文绿色便携专业版