基于boost库的搜索引擎项目

文章目录

  • 一、项目背景
  • 二、什么样的搜索引擎
  • 三、搜索引擎的宏观图原理
  • 四、Parse模块
    • 4.1下载boost库源代码
    • 4.2提取boost库中以.html为结尾的文件
      • 4.2.1 boost库的简单使用
    • 4.3数据清洗(去标签化)
      • 4.3.1数据清洗的具体实现
    • 4.4将清洗后的数据写入到raw.txt文件中
  • 五、正排索引 vs 倒排索引——搜索引擎具体原理
  • 六、建立索引
    • 6.1整体框架
    • 6.2建立索引
      • 6.2.1建立正排索引
      • 6.2.2建立倒排索引
    • 6.3根据文档id获得正排
    • 6.4根据关键字获取倒排
    • 6.5单例实现
  • 七、搜索引擎searcher模块
    • 7.1初始化
    • 7.2查找
    • 7.3优化
  • 八、http_server模块
  • 九、日志
  • 十、部署服务到 linux 上

一、项目背景

boost库是指一些为C++标准库提供扩展的程序库总称,但是boost网站中并没有为我们提供站内搜索功能,因此我们要想找到某一个类的用法还要一个个去找,因此我们这次的目的就是实现一个搜索引擎功能,提高我们获取知识的效率

二、什么样的搜索引擎

比如百度,谷歌,360等,这些都是大型的搜索引擎仅靠我自己一个人肯定是无法完成的,因此我打算实现一个小型的。先来看大多数搜索引擎的共同点
在这里插入图片描述
在这里插入图片描述
我们可以发现搜索出来的内容大致由三部分组成,分别是标题,内容和URL链接,因此我们基于boost库实现的搜索引擎的搜索结果也以这三部分构成

三、搜索引擎的宏观图原理

在这里插入图片描述
我们只是去写一个服务器,让用户在我们的服务器上通过关键词搜索获得相关资源,这些资源可以放在我们服务器本地,也可以是在其他网站上的资源,我们本次就从boost官网上将boost库下载到我们服务器为例

四、Parse模块

将下载到我们服务器本地boost库进行数据清洗,我们这里仅把boost库中以.html为结尾的文件放到我们服务器本地中,因为数据量太大会导致我们服务器运行变慢,那么为什么要进行数据清洗和,也就是去标签等操作,因为在文件中包含大量的双标签和单标签,与我们用户搜索关键词无关,因此我们只把有用的信息进行保存即可。
1.递将下载好的boost库放到input文件夹中
2. 读取 html中的对数据进行清洗(去标签化)和提取标题,正文和URL
3.将数据清洗后的每个文档我们以’\3’作为分隔符,写入到我们创建的data/raw_html/raw.txt文件中。

为什么要用’\3’作为文档分隔符?因为它是不可显示的控制字符,使用其它的不可显控制字符也可以

// 源文件目录,下面存放的是所有的HTML网页
const std::string src_path = "data/input/";
// 处理完成的数据的存放位置
const std::string output = "data/raw_html/raw.txt";typedef struct DocInfo{std::string title;   //文档的标题std::string content; //文档内容std::string url;     //该文档在官网中的url
}DocInfo_t;//我们自己定的命名规则
//const &:输入
//*: 输出
//&: 输入输出bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list);
bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results);
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output);int main()
{// 1.递归式的把每个html文件名带路径,到存到files_list容器中,方便后期进行一个一个的文件进行读取std::vector<std::string> files_list;if (!EnumFile(src_path, &files_list)){std::cerr << "enum file name error!" << std::endl;return 1;}// 2.对files_list中文件的内容读取与分析std::vector<DocInfo_t> results;if (!ParseHtml(files_list, &results)){std::cerr << "parse file name error!" << std::endl;return 2;}// 3.把解析完毕的各个文件内容,写入到output,按照\3作为每个文档的分隔符if (!SaveHtml(results, output)){std::cerr << "Save file name error!" << std::endl;return 3;}return 0;
}

上述三步一句话总结为:把boost库下载到input文件夹下,然后递归式的把以.html结尾的文件进行数据清洗,最后将清洗后的数据写入到/data/raw.txt文件中作为数据源
下面我们来依次实现上述内容

4.1下载boost库源代码

在这里插入图片描述
rz -E放到Linux中,然后解压,将解压后的文件放到/data/input/文件下

4.2提取boost库中以.html为结尾的文件

递归式的把每个后缀为.html的文件,以路径+文件名的方式保存到vector容器files_list中,方便我们后续直接从容器中获取文件路径+文件名,从而读取文件内容

bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list)
{namespace fs = boost::filesystem;fs::path root_path(src_path);//判断路径是否存在,如果不存在就没有必要向后走了if(!fs::exists(root_path)){std::cerr << src_path << "not exists" << std::endl;return false;}//定义一个空的迭代器,用来进行判断递归结束fs::recursive_directory_iterator end;for(fs::recursive_directory_iterator iter(root_path); iter != end; iter++){//1.判断是否是普通文件if(!fs::is_regular_file(*iter)){continue;//不是break}//2.判断文件后缀是否是.htmlif(iter->path().extension() != ".html"){continue;}//std::cout << "debug: " << iter->path().string() << std::endl;//当前的路径一定是一个合法的,以.html结束的普通网页文件files_list->push_back(iter->path().string());//将符合要求的文件放到files_list容器中}return true;
}

4.2.1 boost库的简单使用

boost库的文件操作函数全在filesystem命名空间下,因此我们赋给一个命名空间fs,方便我们使用,以后我们直接使用fs就行了

namespace fs = boost::filesystem;

4.3数据清洗(去标签化)

遍历files_list容器中的每一个文档,提取该文档的标题,正文,和URL,并将这些数据作为成员保存到一个对象中,然后再将这些依次对象放到保存当前对象的vector容器results中。我们提取后的标题,正文和URL中没有标签存在的

typedef struct DocInfo{std::string title;   //文档的标题std::string content; //文档内容std::string url;     //该文档在官网中的url
}DocInfo_t;

ParseHtml整体框架

bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results)
{//int cnt = 3;for(const std::string &file : files_list){//1.读取文件,Read()std::string result;if(!ns_util::FileUtil::ReadFile(file, &result)){continue;}DocInfo_t doc;//2.解析指定的文件,提取titleif(!ParseTitle(result, &doc.title)){continue;}//3.解析指定的文件,提取contentif(!ParseContent(result, &doc.content)){continue;}//4.解析指定的文件路径,构建URLif(!ParseUrl(file, &doc.url)){continue;}//完成解析任务,当前文档的相关结果都保存在了doc里面//results->push_back(doc);//这样写会发生拷贝,效率比较低results->push_back(std::move(doc));//ShowDoc(doc);// cnt--;// if(cnt == 0)// {//     break;// }}return true;
}

4.3.1数据清洗的具体实现

##############获取标题
static bool ParseTitle(const std::string &file, std::string *title)
{std::size_t begin = file.find("<title>");if(begin == std::string::npos){return false;}std::size_t end = file.find("</title>");if(end == std::string::npos){return false;}begin += std::string("<title>").size();if(begin > end){return false;}*title = file.substr(begin, end - begin);return true;
}
##############获取正文
static bool ParseContent(const std::string &file, std::string *content)
{//去标签,基于一个简易的状态机enum status{LABLE,CONTENT};enum status s = LABLE;for(char c : file){switch (s){case LABLE:if(c == '>') s = CONTENT;break;case CONTENT:if(c == '<') s = LABLE;else{//我么不想保留原始文件中的\n,因为我们想用\n作为html解析之后的文本分割符if(c == '\n') c = ' ';content->push_back(c);}default:break;}}return true;
}
################获取URL
static bool ParseUrl(const std::string &file_path, std::string *url)
{std::string url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html/";std::string url_tail = file_path.substr(src_path.size());*url = url_head + url_tail;return true;
}

4.4将清洗后的数据写入到raw.txt文件中

清洗后的文件内容已经以对象的方式被我们保存到results容器中了,因此此时每个文档去标签后的内容,就为容器中其中一个对象的各成员相加之后得到的字符串

我们将文档中的标题,正文,URL以’\3’作为分隔符进行分隔,文档与文档之间以’\n’作为分隔符进行分隔

将每个文件以二进制的方式写入到raw.txt文件中

bool SaveHtml(const std::vector<DocInfo_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());}return true;
}

五、正排索引 vs 倒排索引——搜索引擎具体原理

正排索引: 根据文档ID映射文档内容,只要文档ID正确,就一定能找到对应的文档
在这里插入图片描述
对目标文档进行分词,目的是为了方便建立倒排索引和查找

重庆今年特别热:重庆/今年/特别/热/特别热/不愧/中国/火炉
重庆是中国的四大火炉之首:重庆/中国/四大火炉/火炉/之首
像"的",“呀"之类的词,几乎在所有的文章都有大量出现,不应该作为搜索的关键词,这类词称之为**“暂停词”**,类似的还有"了”、"吗"等等,对此,不给予考虑。

倒排索引: 根据文档内容,分词,整理不重复的各个关键字,对应联系到文档ID的方案
在这里插入图片描述
模拟一次查找的过程: 输入关键词火炉,通过倒排索引提取对应的文档ID,在正排索引中,通过文档ID获取文档内容,对文档内容中的title、conent(desc)、url文档结果进行摘要,最后构成响应结果

此时有两个细节:
细节一:需要用到权值(weight)对搜索到的文档结果进行排序,而权值根据关键词在标题中是否出现以及在内容中出现的次数决定
细节二:搜索的内容可能不是一个关键词,而是一条语句,语句经过分词处理,被处理成多个关键词,多个关键词可能对应着同一个文档,此时需要进行去重处理

六、建立索引

为什么要建立索引?
因为我们的目标是文档既可以通过文档中关键字进行搜索到,也可以通过文档的文件ID被搜索到,
因此我们要分别建立正排索引和倒排索引

6.1整体框架

自定义类型

struct DocInfo{std::string title;   // 文档的标题std::string content; // 文档内容std::string url;     // 该文档在官网中的urluint64_t doc_id;     // 文档的ID};struct InvertedElem
{uint64_t doc_id;std::string word;int weight;
};// 倒排拉链
typedef std::vector<InvertedElem> InvertedList;class Index
{
private:// 正派索引的数据结构用数组,数组的下标天然是文档的IDstd::vector<DocInfo> forward_index; // 正排索引// 倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]std::unordered_map<std::string, InvertedList> inverted_index;
}

为什么要定义出上述两种类型?
正排索引是要通过文档id获取对应的文档内容,一个id只会对应一个文档,因此我们可以利用vector数组来存放文档对象DocInfo,以下标作为文档id。
而倒排索引是要通过关键字来获取对应文档,与正排索引不同的是,通过关键字得到的文档可能是多个,因为不同的文档可能会具有相同的关键字,因此我们选择用map容器,通过实体的std::string关键字去映射一个vector数组,数组中存放的是多个文档

// 倒排拉链
typedef std::vector<InvertedElem> InvertedList;
std::unordered_map<std::string, InvertedList> inverted_index;

并倒排拉链对象中还要保存一个叫权重的值,因为不同的文件包含该关键字的数量不同,包含的多我们认为该文件占有的权重大,权重我们以关键字在标题中出现的次数*10 + 在正文中出现的次数

struct InvertedElem
{uint64_t doc_id;std::string word;int weight;
};
class Index
{
private:// 正派索引的数据结构用数组,数组的下标天然是文档的IDstd::vector<DocInfo> forward_index; // 正排索引// 倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]std::unordered_map<std::string, InvertedList> inverted_index;
public:
//获取单例static Index *GetInstance(){}// 根据去标签,格式化之后的文档,构建正排和倒排索引 --建立索引bool BuildIndex(const std::string &input) {}// parse处理完毕的数据交给我// 根据doc_id找到文档内容 --获得正排DocInfo *GetForwordIndex(uint16_t doc_id) {}// 根据关键字string,获得倒排拉链 --获得倒排InvertedList *GetInvertedList(const std::string &word){}
}

在Index类中我们对外部提供的接口有四个(不包含析构),分别是
1.建立索引(建立正排索引和倒排索引)
2.根据文档关键字获取倒排索引
3.根据文档倒排索引id获取正排索引中的文件
4.获取单例

6.2建立索引

以输入模式或二进制的方式打开数据清理后的文件

bool BuildIndex(const std::string &input) // parse处理完毕的数据交给我
{std::ifstream in(input, std::ios::in | std::ios::binary);if (!in.is_open()){std::cerr << "sorry, " << input << "open error" << std::endl;return false;}std::string line;int count = 0;while (std::getline(in, line)){DocInfo *doc = BuildForwordIndex(line);if (doc == nullptr){std::cerr << "build " << line << " error" << std::endl;continue;}BuildInvertedIndex(*doc);count++;if(count % 50 == 0){//std::cout << "当前已经建立的索引文档:" << count <<std::endl;LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));}}return true;
}

std::getline(in, line)每次读取一行,也就是读到以\n,而遇到一个\n分隔符就意味着一个文件的结束,因此我们是每读取一个文件就去对它建立正排和倒排索引

6.2.1建立正排索引

namespace ns_util{
class StringUtil{public:static void Split(const std::string &target, std::vector<std::string> *out, const std::string &sep){// boost splitboost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);}};
}

boost::token_compress_on :意味将连续的分隔符视为一个分隔符

private:DocInfo *BuildForwordIndex(const std::string &line){// 1.解析line,字符串切分// line -> 3 string, title, content, urlstd::vector<std::string> results;std::string sep = "\3";ns_util::StringUtil::Split(line, &results, sep);if (results.size() != 3){return nullptr;}// 2.字符串进行填充到DocInfoDocInfo doc;doc.title = results[0];doc.content = results[1];doc.url = results[2];doc.doc_id = forward_index.size(); // 先进行保存id,再插入,对应的id就是当前doc在vector中的下标// 3.插入到正排索引的vectorforward_index.push_back(std::move(doc));return &forward_index.back();}

DocInfo *BuildForwordIndex(const std::string &line)参数line是读取的一行,也就是一个文件
先通过boost库中的split函数将读取的字符串(这个文件)进行拆分,拆分后的字符串自然的就放到了一个vector容器中,然后vector容器中的每个元素依次填充到DocInfo对象中,最后将DocInfo对象放到正排索引的vector容器中

6.2.2建立倒排索引

private:bool BuildInvertedIndex(const DocInfo &doc){// DocInfo{title, content, url, doc_id}// word -> 倒排拉链struct word_cnt{int title_cnt;int content_cnt;word_cnt() : title_cnt(0), content_cnt(0) {}};std::unordered_map<std::string, word_cnt> word_map; // 用来暂存词频的映射表// 对标题进行分词std::vector<std::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::vector<std::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 1for (auto &word_pair : word_map){InvertedElem item;item.doc_id = doc.doc_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;}};

建立倒排索引是在基于建立完成正排索引之后的,因为在建立完正排索引之后我们就可以直接使用正排索引的文档id作为倒排索引的文档id,即提高了效率,又保证了同一个文档对应的文档id的唯一性
在词频统计的时候不区分大小写,以便我们在搜索时也可以不区分大小写
1.我们每次读取的是正排索引中的一个文档对象,然后对这个对象的标题和正文进行分词和词频统计,再将词和词频映射到一个临时map容器中
2.遍历这个临时map容器,构建当前文档的文档id,关键词和权重放到倒排拉链元素对象InvertedElem 中
根据关键词找到它在倒排索引inverted_index中的映射值,这个映射值为一个倒排拉链(vector容器),然后我们再把当前关键字对应的元素对象InvertedElem插入到倒排拉链中
注意:索引是索引,拉链是拉链,倒排索引中有拉链,因为它不是的元素一一映射的关系,一个关键字可能对应多个值(多个文档),而在正排索引中,一个文档id只会对应一个文档

我们在对文档进行分词,从而获取关键字的时候,使用了第三方库cppjieba分词

#include "cppjieba/Jieba.hpp"
class JiebaUtil{private:static cppjieba::Jieba jieba;public:static void CutString(const std::string &src, std::vector<std::string> *out){jieba.CutForSearch(src, *out);}};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";//静态成员类外初始化cppjieba::Jieba JiebaUtil::jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH);

6.3根据文档id获得正排

// 根据doc_id找到文档内容 1.获得正排
DocInfo *GetForwordIndex(uint16_t doc_id)
{if (doc_id >= forward_index.size()){std::cerr << "doc_id out range, error!" << std::endl;return nullptr;}return &forward_index[doc_id];
}

6.4根据关键字获取倒排

// 根据关键字string,获得倒排拉链 2.获得倒排
InvertedList *GetInvertedList(const std::string &word)
{auto iter = inverted_index.find(word);if (iter == inverted_index.end()){std::cerr << word << " have no InvertedList" << std::endl;return nullptr;}return &(iter->second);
}

在倒排索引中,找是否存在该关键字如果存在,就把他所映射的vector容器返回,vector容器包含所有具有该关键字的文件

6.5单例实现

private:
Index()
{}Index(const Index &) = delete;
Index &operator=(const Index &) = delete;static Index *instance;
static std::mutex mtx;public:
~Index()
{}static Index *GetInstance()
{if (instance == nullptr){mtx.lock();if (instance == nullptr){instance = new Index();}mtx.unlock();}return instance;
}

因为我们只需服务器上保存一份已经被清洗过且建立完索引的数据就可以,如果每访问一次都要去重新创建一次,就会导致服务器数据冗余,包含多份相同的数据

七、搜索引擎searcher模块

在这里插入图片描述
我们想要的搜索结果是关键词不区分大小写,权重越大(与关键字相关性越强)的文件越靠前,显示的结果没有重复文档,且只需要显示该文档中的部分信息即可。
我们在搜索的时候用户搜索关键字,通过关键字获取倒排索引,再通过倒排索引中的对象id获取正排索引中保存的文件,注意:在搜索栏搜索的是关键字信息或一个语句,不是文件索引id号

7.1初始化

namespace ns_searcher
{class Searcher{private:ns_index::Index *index; // 供系统进行查找的索引public:Searcher() {}~Searcher() {}public:void InitSearcher(const std::string &input){// 1.获取或者建立index对象index = ns_index::Index::GetInstance();//std::cout << "获取index单例成功..." << std::endl;LOG(NORMAL, "获取index单例成功...");// 2.根据index对象建立索引index->BuildIndex(input);//std::cout << "建立正排和倒排索引成功..." << std::endl;LOG(NORMAL, "建立正排和倒排索引成功...");}
}

7.2查找

查找过程分为四个模块
1.对搜索关键词或关键句进行分词
2.将分完的关键词在倒排索引中进行搜索
3.根据搜索结果的权重,将搜出来的文件进行排序
4.再通过排序后的文件id,在正排索引中获取对应的文件内容,返回给用户

// query:搜索关键字
// json_string:返回给用户浏览器的搜索结果
void Search(const std::string &query, std::string *json_string)
{// 1.[分词]:对我们的query进行按照searcher的要求进行分词std::vector<std::string> words;ns_util::JiebaUtil::CutString(query, &words);// 2.[触发]:就是根据分词的各个"词", 进行index查找ns_index::InvertedList inverted_list_all; // 内部InverterElemfor (std::string word : words){boost::to_lower(word);ns_index::InvertedList *inverted_list = index->GetInvertedList(word);if (inverted_list == nullptr){continue;}inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());}// 3.[合并排序]:汇总查找结果,按照相关性(weight)降序排序std::sort(inverted_list_all.begin(), inverted_list_all.end(),[](const ns_index::InvertedElem &e1, const ns_index::InvertedElem &e2){return e1.weight > e2.weight;});// 4.[构建]:根据查找出来的结果,构建json串--jsoncppJson::Value root;for (auto &item : inverted_list_all){ns_index::DocInfo *doc = index->GetForwordIndex(item.doc_id);if (doc == nullptr){continue;}Json::Value elem;elem["title"] = doc->title;elem["desc"] = GetDesc(doc->content, item.word);elem["url"] = doc->url;elem["id"] = (int)item.doc_id;elem["weight"] = item.weight;//int -> stringroot.append(elem);}Json::StyledWriter writer;//Json::FastWriter writer;*json_string = writer.write(root);
}
inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());

将所有关键词对应的倒多个排拉链拉链都汇总插入到同一个倒排拉链中

std::sort(inverted_list_all.begin(), inverted_list_all.end(),[](const ns_index::InvertedElem &e1, const ns_index::InvertedElem &e2){return e1.weight > e2.weight;});

对汇总后的倒排拉链,按照权重将文件进行排序

7.3优化

1.呈现给用户的信息不是把该文件的所有内容都呈现,而是只呈现部分关键信息
因此我们选择在找到word在html_content中的首次出现,然后往前找50字节(如果没有,从begin考试),往后找100字节(如果没有,到end就可以),最后截取出这部分内容

//获取摘要
std::string GetDesc(const std::string &html_content, const std::string &word)
{//找到word在html_content中的首次出现,然后往前找50字节(如果没有,从begin考试),往后找100字节(如果没有,到end就可以的)//截取出这部分内容const int prev_step = 50;const int next_step = 100;//1.找到首次出现//find去找是有坑的//std::size_t pos = html_content.find(word);// if(pos == std::string::npos)// {//     return "None";//这种情况是不可能存在的,因为一定会找到// }//使用忽略大小写查找auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), [](int x, int y){return std::tolower(x) == std::tolower(y);});if(iter == html_content.end()){return "None1";}int pos = std::distance(html_content.begin(), iter);//获取html_content.begin()和iter之间的距离//2.获取start,endint start = 0;int end = html_content.size() - 1;//如果之前有50+字符,就更新开始位置if(pos - prev_step > start) start = pos - prev_step;//否则start值仍然为0if(pos + next_step < end) end = pos + next_step;//3.截取子串,returnif(start >= end) return "None2";return html_content.substr(start, end - start);
}

标准库中的search函数
在这里插入图片描述
查找条件

// using predicate comparison:
bool mypredicate (int i, int j) {
return (i==j);
int needle2[] = {20,30,50};
it = std::search (haystack.begin(), haystack.end(), needle2, needle2+3, mypredicate);if (it!=haystack.end())std::cout << "needle2 found at position " << (it-haystack.begin()) << '\n';
elsestd::cout << "needle2 not found\n";
}

标准库中的distance函数
在这里插入图片描述
计算两个迭代器之间的距离

2.因为同一个关键句中可能包含多个关键词信息,而这多个关键词就会映射多个倒排索引,那么在这些索引对应的倒排拉链中的文件很有可能有一些是相同的,因此我们还要对搜索出来的文件进行去重操作
通过map容器去重

struct InvertedElemPrint{uint64_t doc_id;int weight;std::vector<std::string> words;InvertedElemPrint():doc_id(0), weight(0){}
};
// query:搜索关键字
// json_string:返回给用户浏览器的搜索结果
void Search(const std::string &query, std::string *json_string)
{// 1.[分词]:对我们的query进行按照searcher的要求进行分词std::vector<std::string> words;ns_util::JiebaUtil::CutString(query, &words);// 2.[触发]:就是根据分词的各个"词", 进行index查找//ns_index::InvertedList inverted_list_all; // 内部InverterElemstd::vector<InvertedElemPrint> inverted_list_all;std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;for (std::string word : words){boost::to_lower(word);ns_index::InvertedList *inverted_list = index->GetInvertedList(word);if (inverted_list == nullptr){continue;}//inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());for(const auto &elem : *inverted_list){auto &item = tokens_map[elem.doc_id]; //[]:如果存在直接获取,如果不存在新建//item一定是doc_id相同的print节点item.doc_id = elem.doc_id;item.weight += elem.weight;item.words.push_back(elem.word);}}for(const auto &item : tokens_map){inverted_list_all.push_back(std::move(item.second));}// 3.[合并排序]:汇总查找结果,按照相关性(weight)降序排序// std::sort(inverted_list_all.begin(), inverted_list_all.end(),//           [](const ns_index::InvertedElem &e1, const ns_index::InvertedElem &e2)//           {//               return e1.weight > e2.weight;//           });std::sort(inverted_list_all.begin(), inverted_list_all.end(),[](const InvertedElemPrint &e1, const InvertedElemPrint &e2){return e1.weight > e2.weight;});// 4.[构建]:根据查找出来的结果,构建json串--jsoncppJson::Value root;for (auto &item : inverted_list_all){ns_index::DocInfo *doc = index->GetForwordIndex(item.doc_id);if (doc == nullptr){continue;}Json::Value elem;elem["title"] = doc->title;elem["desc"] = GetDesc(doc->content, item.words[0]);elem["url"] = doc->url;elem["id"] = (int)item.doc_id;elem["weight"] = item.weight;//int -> stringroot.append(elem);}Json::StyledWriter writer;//Json::FastWriter writer;*json_string = writer.write(root);
}

八、http_server模块

利用第三方网络库

#include "searcher.hpp"
#include "cpp-httplib/httplib.h"const std::string input = "data/raw_html/raw.txt";
const std::string root_path = "./wwwroot";int main()
{ns_searcher::Searcher search;search.InitSearcher(input);httplib::Server svr;svr.set_base_dir(root_path.c_str());svr.Get("/s", [&search](const httplib::Request &req, httplib::Response &rsp){if(!req.has_param("word")){rsp.set_content("必须要有搜索关键字!", "text/plain; charset=utf-8");return;}std::string word = req.get_param_value("word");//std::cout << "用户正在搜索:" << word <<std::endl;LOG(NORMAL, "用户搜索的: " + word);std::string json_string;search.Search(word, &json_string);rsp.set_content(json_string, "application/json");});LOG(NORMAL, "服务器启动成功...");svr.listen("0.0.0.0",8080 );return 0;
}

九、日志


#pragma once#include <iostream>
#include <string>
#include <ctime>#define NORMAL  1
#define WARNING 2
#define DEBUG   3
#define FATAL   4#define LOG(LEVEL, MESSAGE) log(#LEVEL, MESSAGE, __FILE__, __LINE__)void log(std::string level, std::string message, std::string file, int line)
{std::cout << "[" << level << "]" << "[" << time(nullptr) << "]" << "[" << message << "]" << "[" << file << " : " << line << "]" << std::endl;
}

十、部署服务到 linux 上

nohup命令 ,英文全称no hang up (不挂起),用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。nohup命令,在默认情况下(非重定向时),会输出一个名叫nohup.out的文件到当前目录下,如果当前目录的nohup out文件不可写,输出重定向到$HOME/nohup.out文件中。

使用命令:nohup ./http_server > log/log.txt 2>&1 &将服务部署到linux上
2>&1 解释:将标准错误2重定向到标准输出1,标准输出1再重定向输入到 log/log.txt 文件中
最后一个&解释:表示后台运行

部署成功后,如果需要关闭服务,则使用kill -9 命令,根据进程pid结束进程即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/744521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++面试问题收集

0 持续更新中 目录 0 持续更新中 1 C语言相关 1.1 malloc/free和new/delete区别 1.2 内存泄漏 1.3 堆区和栈区的区别 1.4 宏定义和const的区别 1.5 多态 1.6 类中的静态成员变量 2 操作系统相关 2.1 进程和&#xff08;用户&#xff09;线程的区别 2.2 系统调用 2.3…

学习SSM的记录(七)-- SSM整合

目录 SSM整合理解 什么是SSM整合 SSM整合核心问题 1.SSM整合需要几个ioc容器 2.每一个ioc容器对应哪些类型组件 ​编辑 3.ioc容器之间关系和调用方向 4.具体多少配置类以及对应容器关系 5.ioc初始化方式和配置位置 ​编辑 SSM整合配置实战 1.数据库准备 2.创建项目…

十分钟教你有效运用百度检索式

十分钟教你有效运用百度检索式 ​ 百度作为国内最大的搜索引擎网站&#xff0c;虽然很多人都喷它的搜索页面广告太多&#xff0c;但是对于不会科学上网的同学来说&#xff0c;似乎除了继续用百度以外也没有更好的选择。 ​ 其实搜索引擎一般都会内涵一些高级的搜索技巧&#…

中间件漏洞(redis)

目录 1.Redis服务器被挖矿案例 2.redis常见用途 3.redis环境配置 4.redis的持久化机制 5.redis动态修改配置 6.webshell提权案例 7.定时任务bash反弹连接提权案例 8.SSH Key提权案例 9.redis安全加固分析 1.Redis服务器被挖矿案例 我没有体验过&#xff0c;那就看看别…

【Redis】Redis常用命令一

1.keys&#xff1a;返回所有满足条件的key&#xff0c;比如&#xff1a; KEYS pattern时间复杂度&#xff1a;O(N)&#xff0c;返回值&#xff1a;匹配pattern的所有key。 • h?llo 匹配 hello , hallo 和 hxllo • h*llo 匹配 hllo 和 heeeello • h[ae]llo 匹配 hello 和 …

【CSP试题回顾】201709-1-打酱油

CSP-201709-1-打酱油 解题代码 #include<iostream> using namespace std;int n, num;int main() { cin >> n;if (n / 50 ! 0){num 7 * (n / 50);n % 50;}if (n / 30 ! 0){num 4 * (n / 30);n % 30;}if (n / 10 ! 0) num (n / 10);cout << num;return…

Flask 专题

[CISCN2019 总决赛 Day1 Web3]Flask Message Board 查看session解密 但不知道密钥&#xff0c;题目说FLASK,那肯定就是找密钥,发现输入什么都没有显示&#xff0c;只有author那里有回显在版上&#xff0c;所以尝试sstl&#xff0c;{{config}}找到密钥 扫目录发现有admin进入…

6个免费的3D模型网站,助你3D建模渲染不用愁

​随着3D建模和渲染技术的不断发展&#xff0c;其重要性也日益增加。创建新的3D模型往往需要耗费大量时间&#xff0c;因此&#xff0c;本文整理了六个顶尖的免费3D模型资源网站&#xff0c;以便您能够方便地获取3D模型&#xff0c;大幅减少建模时间。这些资源有助于您更快地开…

高铁地铁智慧公厕方案与案例介绍

随着轨道交通的快速发展&#xff0c;高铁、地铁、火车成为人们出行的主要交通工具。而在这种规划建设要求极具的场所&#xff0c;信息化、智慧化的管理系统部署必不可少&#xff0c;智慧公厕作为公共设施的重要组成部分&#xff0c;建设和管理也越来越受到重视。智慧公厕系统的…

elasticsearch(学习笔记)(分布式搜索引擎)(黑马)(kibana操作)

一、索引库操作 索引库就类似数据库表&#xff0c;mapping映射就类似表的结构。 我们要向es中存储数据&#xff0c;必须先创建“库”和“表”。 1、mapping映射属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;字段数据类型…

自动驾驶最强学习资料

自动驾驶最强学习资料 资料说明和获取方式 本资料包是作者吐血整理的干货&#xff01;目前为止应该是非常全的自动驾驶资料包&#xff01;是作者五年自动驾驶算法工程师的 积累&#xff01; 以上干货资料并不全是作者自己原创&#xff0c; 是作者作为五年自动驾驶算法工程的…

春招_程序员怎么写简历_写简历网站

你们在制作简历时,是不是基本只关注两件事:简历模板,还有基本信息的填写。 当你再次坐下来更新你的简历时,可能会发现自己不自觉地选择了那个“看起来最好看的模板”,填写基本信息,却没有深入思考如何使简历更具吸引力。这其实是一个普遍现象:许多求职者仍停留在传统简历…

c++的类型转换和IO流

类型转换和IO流 一、c语言的类型转换 int i 1; // 隐式类型转换&#xff0c;这些类型都是表示数据的大小&#xff0c;整型家族包括char相互转化是因为他们只是范围不一样&#xff0c;浮点数与整型支持是因为浮点数本质也是表示数据的大小&#xff0c;只是加了精度而已&#…

vscode-server的搭建方法

一、配置服务器端口支持 1、开放端口&#xff1a; 2、关闭防火墙 systemctl stop firewalld.service systemctl disable firewalld.service二、配置code-server到服务器上** 1、下载code-server-4.22.0-linux-amd64.tar.gz到本地&#xff08;可下载最新的版本&#xff09;&a…

多人语聊房社交APP开发需要有哪些功能呢?

随着移动互联网的快速发展&#xff0c;社交APP已经成为人们日常生活中不可或缺的一部分。而随着语音社交的兴起&#xff0c;多人语聊房社交APP也逐渐受到了用户的青睐。在开发多人语聊房社交APP时&#xff0c;需要具备一系列功能&#xff0c;以满足用户的需求并提供良好的使用体…

Linux的一些常用指令

一、文件中 r w x - 的含义 r&#xff08;read&#xff09;是只读权限&#xff0c; w&#xff08;write&#xff09;是写的权限&#xff0c; x&#xff08;execute&#xff09;是可执行权限&#xff0c; -是没有任何权限。 二、一些指令 # 解压压缩包 tar [-zxvf] 压缩包名…

知名Web3 风险投资公机构Hack VC 四位合伙人将亲临hack.summit()2024亚洲区块链开发者大会

2024-03-14 15:32:24 知名web3风险投资机构Hack VC的四位合伙人——Alex Pack、Ed Roman、Roshun Patel和Rodney Yesep将亲临hack.summit()2024亚洲区块链开发者大会&#xff0c;这一盛事将于2024年4月9日至10日在香港数码港举行。 此次大会由Hack VC主办&#xff0c;并得到Alt…

AI预测福彩3D第8弹【2024年3月14日预测--新算法重新开始计算第五次测试】

兄弟们&#xff0c;感觉要起飞了&#xff01;截止昨天&#xff0c;经过新修正的算法进行3D预测&#xff0c;已经连续3次命中7码了&#xff0c;甚至6码或5码就能命中&#xff01; 昨天的开奖是 3 4 9,我的预测结果可参加上一篇文章 继续乘胜追击&#xff0c;再接再厉&#xff0…

Linux-vim显示乱码

Linux运维工具-ywtool 目录 一.问题二.解决2.1 编辑VIM的配置文件2.2 添加以下内容 一.问题 用vim编辑的时候,中文显示乱码 二.解决 2.1 编辑VIM的配置文件 vim ~/.vimrc #如果这个文件不存在,创建一个即可2.2 添加以下内容 添加完成以后就不会在出现中文乱码了 set fil…

ECRS软件:通过视频分析实现精益生产的利器

在追求高效生产的现代工业体系中&#xff0c;精准的数据分析和科学的流程管理是企业不可或缺的核心竞争力。ECRS软件作为一款专业的工时分析工具&#xff0c;通过视频分析的方式&#xff0c;为企业的精益生产提供了有力支持。使用ECRS软件&#xff0c;企业只需完成三项基础操作…