猪八戒做的网站怎么样学校官网的网址

当前位置: 首页 > news >正文

猪八戒做的网站怎么样,学校官网的网址,成品网站超市源码,南京网站房地产目录 编写建立索引的模块 Index

  1. 设计节点 2.基本结构 3.(难点) 构建索引
  2. 构建正排索引#xff08;BuildForwardIndex#xff09; 2.❗构建倒排索引 3.1 cppjieba分词工具的安装和使用 3.2 引入cppjieba到项目中 倒排索引代码 本篇文章#xff0c;我们将继续项…目录 编写建立索引的模块 Index
  3. 设计节点 2.基本结构 3.(难点) 构建索引
  4. 构建正排索引BuildForwardIndex 2.❗构建倒排索引 3.1 cppjieba分词工具的安装和使用 3.2 引入cppjieba到项目中 倒排索引代码 本篇文章我们将继续项目的部分讲解思路如下 在讲解 index 前我们先 接着上篇文章 来看一下保存 html 文件 采用下面的方案 写入文件中一定要考虑下一次在读取的时候也要方便操作!类似title\3 content\3url \n title\3content\3url \n title\3content\3url \n …方便我们getline(ifsream, line)直接获取文档的全部内容title\3content\3url 为什么使用‘\3’ \3 在ASSCII码表中是不可以显示的字符我们将title、content、url用\3进行区分不会污染我们的文档当然你也可以使用\4等 bool SaveHtml(const std::vectorDocInfo_t results, const std::string output) {#define SEP \3//分割符—区分标题、内容和网址// 打开文件在里面进行写入// 按照二进制的方式进行写入 – 你写的是什么文档就保存什么std::ofstream out(output, std::ios::out | std::ios::binary);if(!out.is_open()){std::cerr open output failed! std::endl;return false;}// 到这里就可以进行文件内容的写入了for(auto item : results){std::string out_string;out_string item.title;//标题out_string SEP;//分割符out_string item.content;//内容out_string SEP;//分割符out_string item.url;//网址out_string \n;//换行表示区分每一个文件// 将字符串内容写入文件中out.write(out_string.c_str(), out_string.size());}out.close();return true; } 接下来我们做一下测试/data/raw_html目录下的 raw.txt 就会填入所有的处理完的 html 文档。 尝试查看 至此我们的parser去标签数据清模块就完成了为了大家能够更好的理解下面是一张关系图 编写建立索引的模块 Index
    首先创建 一个 Index.hpp 文件用来编写 索引模块 该文件主要负责三件事① 构建索引 、② 正排索引 、③ 倒排索引
    构建思路框图 搜索引擎逻辑 用户输入【关键字】搜索 → 【倒排索引】搜出 【倒排拉链】 → 倒排拉链中包含所有关键字有关的文档ID及其权重 → 【正派索引】文档ID得到文档三元素 并按照权重排列呈现给用户 1. 设计节点 正排索引是构建倒排索引的基础通过给到的关键字去倒排索引里查找出文档ID再根据文档ID找到对应的文档内容两个节点结构一个是文档信息的节点一个是倒排对应的节点 namespace ns_index {struct DocInfo //文档信息节点{std::string title; //文档的标题std::string content; //文档对应的去标签后的内容std::string url; //官网文档的urluint64_t doc_id; //文档的ID};struct InvertedElem //倒排对应的节点{uint64_t doc_id; //文档IDstd::string word; //关键字通过关键字可以找到对应的IDint weight; //权重—根据权重对文档进行排序展示}; } 说明 有 doc_id、word 和 weight我们可以通过 word 关键字找到对应的文档ID有文档的信息节点通过倒排找到的文档ID就能够在文档信息节点中找到对应的文档所有内容这两个节点都有doc_id就像MySQL中外键相当于两张表产生了关联 2.基本结构 索引模块最大的两个部分当然是构建正排索引和构建倒排索引其主要接口如下 #pragma once#include iostream #include string #include vector #include fstream #include unordered_map #include mutex #include log.hppnamespace ns_index {struct DocInfo // 文档信息节点{std::string title; // 文档的标题std::string content; // 文档对应的去标签后的内容std::string url; // 官网文档的urluint64_t doc_id; // 文档的ID};struct InvertedElem // 倒排对应的节点{uint64_t doc_id; // 文档IDstd::string word; // 关键字通过关键字可以找到对应的IDint weight; // 权重—根据权重对文档进行排序展示};// 倒排拉链–一个关键字对应了一组文档typedef std::vectorInvertedElem InvertedList;class Index{private:std::vectorDocInfo forward_index; // 正排索引// 倒排索引std::unordered_mapstd::string, InvertedList inverted_index;public:Index() {}~Index() {}public:// 根据去标签格式化后的文档构建正排和倒排索引// 将数据源的路径data/raw_html/raw.txt传给input即可这个函数用来构建索引bool BuildIndex(const std::string input){return true;}// 根据倒排索引的关键字word获得倒排拉链InvertedList *GetInvertedList(const std::string word){//…return nullptr;}// 根据doc_id找到正排索引对应doc_id的文档内容DocInfo GetForwardIndex(uint64_t doc_id){//…return nullptr;}}; } GetInvertedList函数根据倒排索引的关键字word获得倒排拉链和上面类似返回第二个元素 //根据倒排索引的关键字word获得倒排拉链 InvertedList GetInvertedList(const std::string word) {// word关键字不是在 unordered_map 中直接去里面找对应的倒排拉链即可auto iter inverted_index.find(word);if(iter inverted_index.end()) // 判断是否越界{std::cerr have no InvertedList std::endl;return nullptr;}// 返回 unordered_map 中的第二个元素— 倒排拉链return (iter-second); } GetForwardIndex函数根据【正排索引】的 doc_id 找到文档内容数组下标就是 doc_id //根据doc_id找到正排索引对应doc_id的文档内容 DocInfo* GetForwardIndex(uint64_t doc_id) {//如果这个doc_id已经大于正排索引的元素个数则索引失败if(doc_id forward_index.size()){ std::cout doc_id out range, error! std::endl;return nullptr;}return forward_index[doc_id];//否则返回相应doc_id的文档内容 } 3.(难点) 构建索引 构建索引的思路正好和用户使用搜索功能的过程正好相反。思路一个一个文档正排遍历为其每个构建先正排索引后构建倒排索引。 步骤 先把处理干净的文档读取上来是按行读取这样就能读到每个html文档按行读上来每个html文档后我们就可以开始构建正排索引和倒排索引此时就要提供两个函数分别为BuildForwardIndex构建正排索引和 BuildInvertedIndex构建倒排索引 基本代码如下 //根据去标签格式化后的文档构建正排和倒排索引 //将数据源的路径data/raw_html/raw.txt传给input即可这个函数用来构建索引 bool BuildIndex(const std::string input) {//在上面SaveHtml函数中我们是以二进制的方式进行保存的那么读取的时候也要按照二进制的方式读取读取失败给出提示std::ifstream in(input, std::ios::in | std::ios::binary);if(!in.is_open()){std::cerr sory, input open error std::endl;return false;}std::string line;int count 0;while(std::getline(in, line)){DocInfo* doc BuildForwardIndex(line);//构建正排索引if(nullptr doc){std::cerr build line error std::endl;continue;}BuildInvertedIndex(*doc);//有了正排索引才能构建倒排索引count; if(count % 50 0) { std::cout 当前已经建立的索引文档 count 个 std::endl; }}return true; } vscode 下的搜索ctrlf
  5. 构建正排索引BuildForwardIndex 在编写构建正排索引的代码前我们要知道在构建索引的函数中我们是按行读取了每个html文件的每个文件都是这种格式title\3content\3url…构建正排索引就是将DocInfo结构体内的字段进行填充这里我们就需要给一个字符串切分的函数我们写到util.hpp中这里我们又要引入一个新的方法——boost库当中的切分字符串函数split 代码如下 class StringUtil{public://切分字符串static void Splist(const std::string target, std::vectorstd::string *out, const std::string sep){//boost库中的split函数boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);} }; boost::split(out, target, boost::is_any_of(sep), boost::token_compress_on); 第一个参数表示你要将切分的字符串放到哪里第二个参数表示你要切分的字符串第三个参数表示分割符是什么不管是多个还是一个第四个参数它是默认可以不传即切分的时候不压缩不压缩就是保留空格 如字符串为aaaa\3\3bbbb\3\3cccc\3\3d 如果不传第四个参数 结果为aaaa bbbb cccc d如果传第四个参数为boost::token_compress_on 结果为aaaabbbbccccd如果传第四个参数为boost::token_compress_off 结果为aaaa bbbb cccc d 构建正排索引的编写 //构建正排索引 DocInfo BuildForwardIndex(const std::string line) {// 1.解析line字符串切分// 将line中的内容且分为3段原始为title\3content\3url\3// 切分后title content urlstd::vectorstd::string results;std::string sep \3; //行内分隔符ns_util::StringUtil::Splist(line, results, sep);//字符串切分 if(results.size() ! 3) { return nullptr; } // 2.字符串进行填充到DocInfo DocInfo doc; doc.title results[0]; doc.content results[1]; doc.url results[2]; doc.doc_id forward_index.size(); //先进行保存id在插入对应的id就是当前doc在vector中的下标// 3.插入到正排索引的vector forward_index.push_back(std::move(doc)); //使用move可以减少拷贝带来的效率降低return forward_index.back();
    } 2.❗构建倒排索引 原理图 总的思路 对 title 和 content 进行分词使用cppjieba在分词的时候必然会有某些词在 title 和 content 中出现过我们这里还需要做一个处理就是对每个词进行词频统计你可想一下你在搜索某个关键字的时候为什么有些文档排在前面而有些文档排在最后这主要是词和文档的相关性自定义相关性我们有了词和文档的相关性的认识后就要来自己设计这个相关性我们把出现在title中的词其权重更高在content中其权重低一些如让出现在title中的词的词频x10出现在content中的词的词频x1两者相加的结果称之为该词在整个文档中的权重根据这个权重我们就可以对所有文档进行权重排序进行展示权重高的排在前面展示权重低的排在后面展示 我们之前的基本结构代码 //倒排拉链节点 struct InvertedElem{uint64_t doc_id; //文档的IDstd::string word; //关键词int weight; //权重 };//倒排拉链 typedef std::vectorInvertedElem InvertedList;//倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系] std::unordered_mapstd::string, InvertedList inverted_index;//文档信息节点 struct DocInfo{std::string title; //文档的标题std::string content; //文档对应的去标签之后的内容std::string url; //官网文档urluint64_t doc_id; //文档的ID }; 需要对 title content都要先分词 – 使用jieba分词 title: 吃/葡萄/吃葡萄(title_word) content吃/葡萄/不吐/葡萄皮(content_word) 词频统计 它是包含标题和内容的我们就需要有一个结构体来存储每一篇文档中每个词出现在title和content中的次数伪代码如下 //词频统计的结点 struct word_cnt {int title_cnt; //词在标题中出现的次数int content_cnt;//词在内容中出现的次数 } 统计这些次数之后我们还需要将词频和关键词进行关联文档中的每个词都要对应一个词频结构体这样我们通过关键字就能找到其对应的词频结构体通过这个结构体就能知道该关键字在文档中的title和content中分别出现了多少次下一步就可以进行权重的计算。这里我们就可以使用数据结构unordered_map来进行存储。 伪代码如下 //关键字和词频结构体的映射 std::unordered_mapstd::string, word_cnt word_map; //关键字就能找到其对应的词频结构体 for(auto word : title_word) {// 一个关键词 对应 标题 中出现的次数word_map[word].title_cnt; //吃1/葡萄1/吃葡萄1 }//范围for进行遍历对content中的词进行词频统计 for(auto word : content_word) {// 一个关键词 对应 内容 中出现的次数word_map[word].content_cnt; //吃1/葡萄1/不吐1/葡萄皮1 } 自定义相关性 知道了在文档中标题 和 内容 每个词出现的次数接下来就需要我们自己来设计相关性了伪代码如下 //遍历刚才那个unordered_mapstd::string, word_cnt word_map; for(auto word : word_map) {struct InvertedElem elem;//定义一个倒排拉链节点然后填写相应的字段elem.doc_id 123;elem.word word.first; // word.first- 关键字elem.weight 10*word.second.title_cnt word.second.content_cnt ;//权重计算// 将关键字 对应的 倒排拉链节点 保存到 对应的倒排拉链这个 数组中inverted_index[word.first].push_back(elem);//最后保存到倒排索引的数据结构中 }//倒排索引结构如下 一个关键字 对应的 倒排拉链一个或一组倒排节点 //std::unordered_mapstd::string, InvertedList inverted_index;//倒排索引结构体 – 一个倒排拉链节点 struct InvertedElem {uint64_t doc_id; // 文档IDstd::string word; // 文档相关关键字int weight; // 文档权重 };//倒排拉链 typedef std::vectorInvertedElem InvertedList; 至此就是倒排索引比较完善的原理介绍和代码思路 3.1 cppjieba分词工具的安装和使用 获取链接cppjieba 下载链接 里面有详细的教程 我们这里可以使用 git clone如下git clone https://github.com/yanyiwu/cppjieba 查看 cppjieba 目录里面包含如下 这是别人的写好的一个开源项目我们来测试一下~ 要注意头文件的路径我们先来修改一下头文件的路径它本身是要使用cppjieba/Jieba.hpp的我们看一下这个头文件的具体路径 路径是cppjieba/include/cppjieba/Jieba.hpp 我们发现 我们可以找到开源项目中的测试文件来进行 test 引入这个头文件我们不能直接引入需要使用软连接 尝试编译后发现 此时我们还是需要对这个头文件进行软连接我们通过查找发现有这么一个路径 但是里面什么东西都没有这是我在联系项目中出现的问题经过我去GitHub查找一番后发现它在另外一个压缩包里 下载好如下: 进入它里面看是否有我们需要的内容 此时我们只需要将它 拷贝到 include/cppjieba/ 下即可 cp -r limonp/include/limonp/ test/cppjieba/include/cppjieba/ 我们将路径都完善之后接下来我们 选取局部测试代码 编译运行一下 int main(int argc, char** argv) {cppjieba::Jieba jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH); vectorstring words; // 接收切分的词string s;s 小明硕士毕业于中国科学院计算所;cout 原句 s endl;jieba.CutForSearch(s, words);cout 分词后;for (auto word : words){cout word | ;}cout endl; } 熟悉完上面的操作后就可以在我们的项目中引入头文件来使用cppjieba分词工具啦~ 3.2 引入cppjieba到项目中
    将软链接建立好之后我们在util.hpp中编写一个jieba分词的类主要是为了方便后期其他地方需要使用的时候可以直接调用。 首先我们先看一下之前的测试代码 可以发现分词用到的 词库和函数 我们在util.hpp中创建一个 JiebaUtil的分词工具类建立软连接 util.hpp代码如下 #pragma once #include iostream #include string #include fstream #include vector#include boost/algorithm/string.hpp #include cppjieba/Jieba.hpp namespace ns_util {class FileUtil{ public://输入文件名将文件内容读取到out中static bool ReadFile(const std::string file_path, std::string *out){// 读取 file_path一个.html文件 中的内容 – 打开文件std::ifstream in(file_path, std::ios::in);//文件打开失败检查if(!in.is_open()){std::cerr open file file_path error std::endl;return false;}//读取文件内容std::string line;//while(bool),getline的返回值istream会重载操作符bool读到文件尾eofset被设置并返回false//如何理解getline读取到文件结束呢getline的返回值是一个while(bool), 本质是因为重载了强制类型转化while(std::getline(in, line)) // 每循环一次读取的是文件的一行内容{*out line; // 将文件内容保存在 *out 里面}in.close(); // 关掉文件return true;}};class StringUtil{public://切分字符串static void Splist(const std::string target, std::vectorstd::string *out, const std::string sep){//boost库中的split函数boost::split(out, target, boost::is_any_of(sep), boost::token_compress_on);}};//下面这5个是分词时所需要的词库路径const char const DICT_PATH ./dict/jieba.dict.utf8; const char* const HMM_PATH ./dict/hmm_model.utf8; const char* const USER_DICT_PATH ./dict/user.dict.utf8; const char* const IDF_PATH ./dict/idf.utf8; const char* const STOP_WORD_PATH ./dict/stop_words.utf8; class JiebaUtil { private: static cppjieba::Jieba jieba; //定义静态的成员变量需要在类外初始化 public: static void CutString(const std::string src, std::vectorstd::string *out) { //调用CutForSearch函数第一个参数就是你要对谁进行分词第二个参数就是分词后的结果存放到哪里jieba.CutForSearch(src, *out); } };//类外初始化就是将上面的路径传进去具体和它的构造函数是相关的具体可以去看一下源代码cppjieba::Jieba JiebaUtil::jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH); } 倒排索引代码 构建倒排索引相对复杂一些只要将上面倒排索引的原理和伪代码的思路理解到位后下面的代码就比较简单了。 //构建倒排索引 bool BuildInvertedIndex(const DocInfo doc)
    { //词频统计结构体 struct word_cnt { int title_cnt; int content_cnt; word_cnt():title_cnt(0), content_cnt(0){} }; std::unordered_mapstd::string, word_cnt word_map; //用来暂存词频的映射表 //对标题进行分词 std::vectorstd::string title_words; ns_util::JiebaUtil::CutString(doc.title, title_words); //对标题进行词频统计 for(auto s : title_words) { boost::to_lower(s); // 将我们的分词进行统一转化成为小写的 word_map[s].title_cnt;//如果存在就获取不存在就新建 } //对文档内容进行分词 std::vectorstd::string content_words; ns_util::JiebaUtil::CutString(doc.content, content_words); //对文档内容进行词频统计 for(auto s : content_words) { boost::to_lower(s); // 将我们的分词进行统一转化成为小写的 word_map[s].content_cnt; } #define X 10
    #define Y 1 //最终构建倒排 for(auto word_pair : word_map) { InvertedElem item; item.doc_id doc.doc_id; //倒排索引的id即文档id item.word word_pair.first; item.weight X * word_pair.second.title_cnt Y * word_pair.second.content_cnt; InvertedList inverted_list inverted_index[word_pair.first]; inverted_list.push_back(std::move(item)); } return true;
    }
    下篇文章将继续对项目进行讲解~ 补充 写项目突然想到一个有意思的点~