基于Boost库、Jsoncpp、cppjieba、cpp-httplib等构建Boost搜索引擎

头像
⭐️个人主页:@小羊
⭐️所属专栏:项目
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录

    • 项目背景
    • 技术栈和项目环境
    • 正排索引和倒排索引
    • 数据去标签与清洗
      • 下载数据源
      • 去标签
    • 建立索引
      • 构建正排索引
      • 构建倒排索引
    • 建立搜索引擎
    • http_server 服务
    • 搜索到的内容有重复


文章末有源码链接。

项目背景

目前我们常用的搜索引擎有Google、百度、360等,这些搜索引擎都是超大型超完善的全网搜索,而本项目Boost搜索引擎只是一个非常简单的站内搜索。

比较维度全网搜索站内搜索
搜索范围与数据来源覆盖整个互联网,数据来源广泛,需搜索引擎爬虫抓取收录网页限定在特定网站内部,数据仅来源于该网站自身内容
搜索效率范围广、数据量大,检索复杂,速度相对较慢,结果筛选耗时搜索范围小,速度更快,可快速定位信息
可控性用户和网站管理者无法干涉搜索引擎算法,搜索结果不可控网站管理者可优化搜索功能,根据需求调整搜索算法等,具有可控性
索引构建需构建庞大复杂的索引系统处理海量数据,技术难度高针对特定网站内容和数据结构优化,索引构建相对简单且更具针对性

为什么选做Boost的搜索引擎呢?
作为C++选手,相信大家都浏览过Boost官网,而我们在2023年之前浏览Boost官网时是没有搜索功能的,虽然自从2023年起新增了搜索功能,但这之前给我们的不太好的浏览体验可能还是耿耿于怀,所以本项目选做Boost搜索引擎,算是弥补之前没有的遗憾吧(虽然肯定没有现在官网提供的好用🤡)。

我们最熟悉最常用的站内搜索cplusplus官网,当我们想查看 vector 的官方文档时可以直接在搜索框中搜索,就能得到我们想要的信息。

虽然我们无法独立实现向百度这样大型的搜索引擎,但是通过本项目Boost搜索引擎这个站内搜索小项目,可以做到管中窥豹的效果,大致了解像他们这样大型的工程整体框架是什么样的,是怎么运作的。

首先我们来看看百度搜索是以什么样的方式展示的:
在这里插入图片描述
可以看到基本有这三部分内容,(当然还有图片,为了简单我们就不展示图片了😜)那本项目也就模仿这样的格式展示搜索到的结果。

另外,当我们的搜索语句中有多个搜索关键词的时候,它是不严格匹配的,因此我们需要有一个切分搜索关键字的过程。这个任务可以借助 cppjieba 这个库来帮我们完成。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

搜索引擎的宏观原理:
在这里插入图片描述

本项目实现的是红色框框中的内容。


技术栈和项目环境

  • 技术栈:C/C++、C++11、STL、Boost库、JsonCpp、cppjieba、cpp-httplib;
  • 项目环境:Ubuntu-22.04、vscode、gcc/g++、makefile。
  • cppjieba 是一个用 C++ 实现的中文分词库,它具有高效、准确、易用等特点;
  • cpp-httplib 是一个轻量级、跨平台的 C++ HTTP 库,它以单头文件的形式存在,使用起来非常便捷。

正排索引和倒排索引

首先我们通过一个例子来了解下什么是正排和倒排索引:

  • 文档1:小帅是安徽理工大学的三好学生
  • 文档2:小帅是安徽理工大学电信院的学生会主席

正排索引:从文档ID找到文档内容(文档中的关键字)。

文档ID文档内容
1小帅是安徽理工大学的三好学生
2小帅是安徽理工大学电信院的学生会主席

目标文档进行分词(方便建立倒排索引和查找):

  • 文档1:小帅、安徽理工大学、三好学生、学生
  • 文档2:小帅、安徽理工大学、电信院、学生、学生会、主席

倒排索引:根据文档内容分词,整理不重复的关键字,找到对应文档ID的方案。

关键字文档ID
小帅文档1、文档2
安徽理工大学文档1、文档2
三好学生文档1
学生文档1、文档2
电信院文档2
学生会文档2
主席文档2

当用户输入学生:倒排索引中查找 -> 提取文档ID -> 根据正排索引 -> 找到文档内容 ->
title+desc+url -> 构建响应结果。

文档1和文档2中都有学生这个关键字,那先显示谁呢?我们后面在搭建的时候会给每个文档设置权重。


数据去标签与清洗

下载数据源

首先从Boost官网下载数据源:
在这里插入图片描述

下载好后通过 rz -E 拉取到Ubuntu服务器上,然后 tar xzf 解压,我们只需要 boost_1_88_0\doc\html 中的内容,将所有内容拷贝到新目录中,其他的部分就可以删除掉了,得到下面这些文件:

在这里插入图片描述

把每个html文件名和路径保存起来,方便后续文件读取:

#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>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;
}DocInfo_t;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) == false) // 判断路径是否存在{std::cerr << src_path << " not exists" << std::endl;return false;}fs::recursive_directory_iterator end;for (fs::recursive_directory_iterator it(root_path); it != end; it++){if (fs::is_regular_file(*it) == false) // 判断是否是普通文件{continue;}if (it->path().extension() != ".html") // 判断文件路径名是否符合要求{continue;}std::cout << "debug: " << it->path().string() << std::endl;files_list->push_back(it->path().string());}return true;
}bool ParseHtml(const std::vector<std::string> &files, std::vector<DocInfo_t> *results)
{return true;
}bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{return true;
}int main()
{// 1.把每个html文件名和路径保存起来,方便后续文件读取std::vector<std::string> files_list;if (EnumFile(src_path, &files_list) == false){std::cerr << "enum file name fail!" << std::endl;return 1;}// 2.按照files_list读取每个文件的内容,并进行解析std::vector<DocInfo_t> results;if (ParseHtml(files_list, &results) == false){std::cerr << "parse html fail!" << std::endl;return 2;}// 3.把解析完毕的各个文件内容写入到output中,按照 \3 作为每个文档的分隔符if (SaveHtml(results, output) == false){std::cerr << "save html fail!" << std::endl;return 3;}return 0;
}

Boost库不是C++标准库,因此在编写makefile时别忘了链接指定库哦:

cc=g++parser : parser.cc$(cc) -o $@ $^ -lboost_system -lboost_filesystem -std=c++11.PHONY:clean
clean:rm -f parser

通过打印调式,我们就能得到下面这些信息:
在这里插入图片描述


去标签

什么是标签?我们随便打开一个上面的文件:
在这里插入图片描述

  • 标签对我们搜索是没有价值的,因此需要去掉这些标签,剩下的内容就是我们需要的;
  • 我们的目标是把每个文档都去标签,然后把内容写入到同一个文件中,每个文档内容不需要任何换行,文档和文档之间用 \3 区分,这样做是为了读取的时候更方便;
  • 比如:title\3content\3url \n title\3content\3url...,用 getline(ifstream, line) 直接读取一个文档的全部内容,然后再根据 \3 获取各个部分。

按照files_list读取每个文件的内容,并进行解析:

bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results)
{for (const std::string &file : files_list){std::string result;if (yjz_util::FileUtil::ReadFile(file, &result) == false) continue;DocInfo_t doc;if (ParseTitle(result, &doc.title) == false) continue;if (ParseContent(result, &doc.content) == false) continue;if (ParseUrl(file, &doc.url) == false) continue;results->push_back(std::move(doc));}return true;
}

提取title:
在这里插入图片描述

static bool ParseTitle(const std::string &file, std::string *title)
{size_t begin = file.find("<title>");if (begin == std::string::npos) return false;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;
}

提取content,也就是去标签,我们只需要像下面这种白色的内容:
在这里插入图片描述

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; // 如果遇到'>',假设接下来是contentcase CONTENT : if (c == '<') s = LABLE; // 如果假设错误,状态重新转为lableelse{// 后面我们想用\n作为html解析后文本的分隔符if (c == '\n') c = ' ';content->push_back(c);}break;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_88_0/doc/html";std::string url_tail = file_path.substr(src_path.size());*url = url_head + url_tail;return true;
}

将解析的内容写入到指定的文件中:

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() == false){std::cerr << "open " << output << " fail!" << std::endl;return false;}for (auto &it : results){std::string out_string;out_string = it.title;out_string += SEP;out_string += it.content;out_string += SEP;out_string += it.url;out_string += '\n';out.write(out_string.c_str(), out_string.size());}out.close();return true;
}

建立索引

index.hpp 的基本结构:

namespace yjz_index
{struct DocInfo{std::string title;std::string content;std::string url;uint64_t doc_id;};struct InvertedElem{uint64_t doc_id;     // 文档IDstd::string word;    // 关键字int weight;          // 权重};class Index{private:Index(){}Index(const Index&) = delete;Index& operator=(const Index&) = delete;static Index *_instance;static std::mutex _mutex;public:using InvertedList = std::vector<InvertedElem>;~Index(){}// 获取单例static Index* GetInstance(){}// 根据文档ID找到文档内容DocInfo* GetForwardIndex(uint64_t doc_id){}// 根据关键字找到倒排拉链InvertedList* GetInvertedList(const std::string &word){}// 根据格式化后的文档,构建正排、倒排索引bool BuildIndex(const std::string &input){}private:// 构建正排索引DocInfo* BuildForwardIndex(const std::string &line){}// 构建倒排索引bool BuildInvertedIndex(const DocInfo &doc){}private:// 将数组下标作为文档IDstd::vector<DocInfo> _forward_index;  // 正排索引std::unordered_map<std::string, InvertedList> _inverted_index;};Index* Index::_instance= nullptr;std::mutex Index::_mutex;
}

构建正排索引

将符合特定格式的字符串解析并转化为结构化的文档信息对象,进而添加到正排索引数据结构(_forward_index 容器 )中,为后续基于文档信息的检索、分析等操作提供基础。

DocInfo* BuildForwardIndex(const std::string &line)
{// 解析line,字符串切分std::vector<std::string> results;const std::string sep = "\3";yjz_util::StringUtil::Split(line, &results, sep);if (results.size() != 3){return nullptr;}// 将切分好的字符串构建DocInfoDocInfo doc;doc.title = results[0];doc.content = results[1];doc.url = results[2];doc.doc_id = _forward_index.size(); // 先更新文档ID再插入_forward_index.push_back(std::move(doc));return &_forward_index.back();
}

构建倒排索引

对给定的文档进行分词处理,统计每个单词在标题和内容中的出现次数,计算每个单词的权重,然后将这些信息添加到倒排索引中。通过这种方式,可以快速查找包含特定单词的文档,并根据单词的权重对文档进行排序。

// 构建倒排索引
bool BuildInvertedIndex(const DocInfo &doc)
{// doc -> 倒排拉链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;yjz_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;yjz_util::JiebaUtil::CutString(doc.content, &content_words);for (auto &s : content_words){boost::to_lower(s); word_map[s].content_cnt++;}for (auto &word_pair : word_map){InvertedElem item;item.doc_id = doc.doc_id;item.word = word_pair.first;item.weight = 10 * word_pair.second.title_cnt + word_pair.second.content_cnt;InvertedList &inverted_list = _inverted_index[word_pair.first];inverted_list.push_back(std::move(item));}return true;
}

Boost库切分字符串:

static void CutString(const std::string &line, std::vector<std::string> *result, const std::string &sep)
{boost::split(*result, line, boost::is_any_of(sep), boost::token_compress_on);
}
  • boost::split 函数:这是 Boost 库中的一个函数,用于将字符串按照指定的分隔符进行分割;
  • *result:通过解引用指针 result,将分割后的子字符串存储到该向量中;
  • line:待分割的输入字符串;
  • boost::is_any_of(sep):用于指定分割字符串时使用的分隔符;
  • boost::token_compress_on:这是一个分割标志,设置为 boost::token_compress_on 表示如果连续出现多个分隔符,会将它们视为一个分隔符进行处理,避免产生空的子字符串

建立搜索引擎

searcher.hpp 基本框架:

namespace yjz_searcher
{class Searcher{public:Searcher(){}~Searcher(){}struct InvertedElemPrint{uint64_t doc_id;int weight;std::vector<std::string> words; // 多个词对应同一个doc_idInvertedElemPrint() : doc_id(0), weight(0) {}};void InitSearcher(const std::string &input){// 获取或创建index对象_index = yjz_index::Index::GetInstance();std::cout << "获取或创建index单例成功!" << std::endl;_index->BuildIndex(input);std::cout << "建立正排和倒排索引成功!" << std::endl;}void Search(const std::string &query, std::string *json_string){// 1.分词,对query(搜索关键字)按要求进行分词// 2.触发,根据分好的词进行索引查找,关键字需要忽略大小写// 3.合并排序,汇总查找结果,按照权重排降序// 4.根据查找出来的结果,构建Json串}// 获取摘要std::string GetDesc(const std::string &html_content, const std::string &word){}private:yjz_index::Index *_index; };
}

编写Search函数:

void Search(const std::string &query, std::string *json_string)
{// 1.分词,对query(搜索关键字)按要求进行分词std::vector<std::string> words;yjz_util::JiebaUtil::CutString(query, &words);// 2.触发,根据分好的词进行索引查找,关键字需要忽略大小写yjz_index::Index::InvertedList inverted_list_all;for (auto word : words){boost::to_lower(word);yjz_index::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.合并排序,汇总查找结果,按照权重排降序std::sort(inverted_list_all.begin(), inverted_list_all.end(), [](const yjz_index::InvertedElem& e1, const yjz_index::InvertedElem& e2){return e1.weight > e2.weight;});// 4.根据查找出来的结果,构建Json串Json::Value root;for (auto &it : inverted_list_all){// 根据文档ID进行正排索引yjz_index::DocInfo *doc = _index->GetForwardIndex(it.doc_id); if (doc == nullptr) continue;Json::Value elem;elem["title"] = doc->title;elem["desc"] = GetDesc(doc->content, it.word);elem["url"] = doc->url;// for Debug// elem["id"] = it.doc_id;// elem["weight"] = it.weight;root.append(elem);}Json::StyledWriter writer;*json_string = writer.write(root);
}

获取摘要:找到word关键字在html_content中首次出现的位置,规定往前找50字节,往后找100字节,截取这部分内容。

因为我们在构建倒排索引和索引查找时将关键字统一转换为了小写,因此在原始数据中查找时也应该统一按小写字母查找。

  • search 函数定义在 <algorithm> 头文件中,用于在一个序列中查找另一个序列首次出现的位置,并支持自定义查找规则。
std::string GetDesc(const std::string &html_content, const std::string &word)
{const int pre_step = 50;const int next_step = 100;// 找到首次出现auto it = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), [](char x, char y){return std::tolower(x) == std::tolower(y);});if (it == html_content.end()) return "None1";int pos = std::distance(html_content.begin(), it);int start = 0;int end = html_content.size() - 1;start = std::max(start, pos - pre_step);end = std::min(end, pos + next_step);if (start >= end) return "None2";return html_content.substr(start, end - start);
}

我们想知道现在的搜索结果是不是按照我们预想的按照权重 weight 进行顺序呈现的呢?
search 函数中构建Json串时,我们把文档ID和权重加上进行测试:
在这里插入图片描述

下面是搜索结果:
在这里插入图片描述

在这里插入图片描述
可以看到是没有问题的。


http_server 服务

下载 cpp-httplib 库,然后直接参照给的示例编写我们想要的服务,非常简单。

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

当然我们也可以自己搭建http服务。


到这里后端的工作基本已经完成了,那前端代码怎么办呢?我这里就直接让Deepseek帮我写了,如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><title>Boost 智能搜索引擎</title><style>:root {--primary-color: #4e6ef2;--hover-color: #3b5bdb;--background: #f8f9fa;--text-dark: #2d3436;--text-light: #636e72;}* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', system-ui, sans-serif;}body {background: var(--background);min-height: 100vh;padding: 2rem 1rem;}.container {max-width: 800px;margin: 0 auto;animation: fadeIn 0.5s ease;}.search-box {display: flex;gap: 10px;margin-bottom: 2rem;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);border-radius: 30px;background: white;padding: 5px;}.search-input {flex: 1;padding: 1rem 1.5rem;border: none;border-radius: 30px;font-size: 1.1rem;color: var(--text-dark);transition: all 0.3s ease;}.search-input:focus {outline: none;box-shadow: 0 0 0 3px rgba(78, 110, 242, 0.2);}.search-btn {padding: 0 2rem;border: none;border-radius: 30px;background: linear-gradient(135deg, var(--primary-color), var(--hover-color));color: white;font-size: 1rem;font-weight: 600;cursor: pointer;transition: all 0.3s ease;display: flex;align-items: center;gap: 8px;}.search-btn:hover {background: var(--hover-color);transform: translateY(-1px);}.result-item {background: white;border-radius: 12px;padding: 1.5rem;margin-bottom: 1rem;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);transition: transform 0.2s ease;}.result-item:hover {transform: translateX(5px);}.result-title {color: var(--primary-color);font-size: 1.2rem;font-weight: 600;margin-bottom: 0.5rem;text-decoration: none;display: flex;align-items: center;gap: 8px;}.result-title:hover {text-decoration: underline;}.result-desc {color: var(--text-dark);line-height: 1.6;margin-bottom: 0.5rem;display: -webkit-box;-webkit-line-clamp: 3;-webkit-box-orient: vertical;overflow: hidden;}.result-url {color: var(--text-light);font-size: 0.9rem;font-family: monospace;}.loading {text-align: center;padding: 2rem;color: var(--text-light);}@keyframes fadeIn {from { opacity: 0; transform: translateY(20px); }to { opacity: 1; transform: translateY(0); }}@media (max-width: 768px) {.search-box {flex-direction: column;border-radius: 15px;}.search-btn {padding: 1rem;justify-content: center;}}</style>
</head>
<body><div class="container"><div class="search-box"><input type="text" class="search-input" placeholder="请输入搜索关键词..." autofocus><button class="search-btn" onclick="search()"><i class="fas fa-search"></i>搜索</button></div><div class="result-container"></div></div><script>// 增强功能$(document).ready(() => {// 回车键搜索$('.search-input').keypress(e => e.which === 13 && search())// 输入框交互$('.search-input').focus(function() {if (this.value === "请输入搜索关键词...") this.value = ""}).blur(function() {if (this.value === "") this.value = "请输入搜索关键词..."})})function search() {const query = $('.search-input').val().trim()if (!query) return// 显示加载状态$('.result-container').html(`<div class="loading"><i class="fas fa-spinner fa-spin"></i>正在搜索中...</div>`)$.ajax({url: `/s?word=${encodeURIComponent(query)}`,method: 'GET',success: buildResults,error: () => {$('.result-container').html(`<div class="result-item" style="color: #dc3545;"><i class="fas fa-exclamation-triangle"></i>请求失败,请稍后重试</div>`)}})}function buildResults(data) {const container = $('.result-container').empty()if (data.length === 0) {container.html(`<div class="result-item"><div style="color: var(--text-light); text-align: center;"><i class="fas fa-search-minus"></i>没有找到相关结果</div></div>`)return}data.forEach(item => {const elem = $(`<div class="result-item"><a href="${item.url}" class="result-title" target="_blank"><i class="fas fa-link"></i>${item.title}</a><p class="result-desc">${item.desc}</p><div class="result-url">${item.url}</div></div>`)container.append(elem)})}</script>
</body>
</html>

最后结果展示:
在这里插入图片描述
可以看到非常完美,Deepseek写的页面还是非常好看的。

但是目前的代码还有一个不易察觉的问题,当我们输入搜索内容,通过 cppjieba 分词得到多个关键词,这些关键词可能都来自同一个文档,根据目前的代码每个关键词都会通过索引查找到这个文档,也就是说这个文档会给我们呈现多份,而我们希望得到的只是一个文档就行,因此接下来还需要优化一下去重的问题。


搜索到的内容有重复

下面是一个测试文件:
在这里插入图片描述

在这里插入图片描述
可以看到通过 cppjieba 分词然后通过每个关键词都索引到了这个文档,给我们重复呈现了四次。

接下来考虑如何去重。我们可以根据一个文档只有一个 doc_id 的特点,将所有 doc_id 相同的关键词统计到一起,权重累加。

struct InvertedElemPrint
{uint64_t doc_id;int weight;std::vector<std::string> words; // 多个词对应同一个doc_idInvertedElemPrint() : doc_id(0), weight(0) {}
};
//... void Search(const std::string &query, std::string *json_string)
{// 1.分词,对query(搜索关键字)按要求进行分词std::vector<std::string> words;yjz_util::JiebaUtil::CutString(query, &words);// 2.触发,根据分好的词进行索引查找,关键字需要忽略大小写// yjz_index::Index::InvertedList inverted_list_all;std::unordered_map<uint64_t, InvertedElemPrint> tokens_map; // 通过doc_id去重for (auto word : words){boost::to_lower(word);yjz_index::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]; // 根据doc_id找到相同的索引节点item.doc_id = elem.doc_id;item.weight += elem.weight; // 权重累加item.words.push_back(elem.word); // 将相同doc_id的关键词管理到一起}}std::vector<InvertedElemPrint> inverted_list_all; // 保存不重复的倒排拉链节点for (const auto &item : tokens_map){inverted_list_all.push_back(std::move(item.second));}// 3.合并排序,汇总查找结果,按照权重排降序// std::sort(inverted_list_all.begin(), inverted_list_all.end(), //     [](const yjz_index::InvertedElem& e1, const yjz_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串Json::Value root;for (auto &it : inverted_list_all){// 根据文档ID进行正排索引yjz_index::DocInfo *doc = _index->GetForwardIndex(it.doc_id); if (doc == nullptr) continue;Json::Value elem;elem["title"] = doc->title;elem["desc"] = GetDesc(doc->content, it.words[0]);elem["url"] = doc->url;// for Debug// elem["id"] = it.doc_id;// elem["weight"] = it.weight;root.append(elem);}// Json::StyledWriter writer;Json::FastWriter writer;*json_string = writer.write(root);
}

在这里插入图片描述
在这里插入图片描述
完成去重结果。

最后我们可以通过下面的指令将服务放到后台运行,方便我们随时搜索。

nohup ./http_server &

Boost搜索引擎源码


本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

头像

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

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

相关文章

QMK机械键盘固件开发指南:从源码到实践

QMK机械键盘固件开发指南&#xff1a;从源码到实践 前言 QMK&#xff08;Quantum Mechanical Keyboard&#xff09;是一款开源的键盘固件&#xff0c;支持众多自定义键盘的功能配置。通过QMK&#xff0c;您可以完全掌控键盘的每一个按键&#xff0c;实现复杂的宏指令、多层按…

WPF 导航

WPF 导航相关控件/机制 控件 / 类说明常用属性/方法Frame用来承载不同的页面 (Page) 并在它们之间切换的容器。Source&#xff08;导航到的 URI&#xff09; Navigate()&#xff08;导航方法&#xff09; CanGoBack / GoBack() CanGoForward / GoForward()Page表示一个单独的可…

时序建模演进之路:从 MLP、RNN 到 LSTM 与 GRU

时序建模演进之路&#xff1a;从 MLP、RNN 到 LSTM 与 GRU 您是否好奇机器如何能像人类一样理解、生成流畅的文本&#xff0c;甚至是从海量代码中自动生成文档&#xff1f;这些自然语言处理 (NLP) 领域的迷人挑战&#xff0c;其核心在于模型处理和记忆 序列数据 的能力。 然而…

【Redis——数据类型和内部编码和Redis使用单线程模型的分析】

文章目录 Redis的数据类型和内部编码单线程模型的工作过程Redis在处理命令时虽然是一个单线程模型&#xff0c;为啥效率那么高&#xff0c;速度快呢&#xff1f; 总而言之&#xff0c;Redis提供的哈希表容器并不一定真的是真的哈希表&#xff0c;而是在特点的场景下&#xff0c…

鸿蒙NEXT开发动画(风格的旋转加载动画组件)

1.创建空白项目 2.Page文件夹下面新建Spin.ets文件&#xff0c;代码如下&#xff1a; /*** SpinKit 风格的旋转加载动画组件。** component* param spinSize - 动画容器大小&#xff08;必须为正数&#xff09;* param spinColor - 动画颜色&#xff08;支持资源引用&#xf…

后端接口请求http改为https

1、使用 OpenSSL 生成自签名证书 在Linxu服务器上执行如下命令&#xff1a; openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes 运行此命令后&#xff0c;会提示输入一些信息&#xff08;如国家、省份、城市、组织名称等&#xff09;&…

工作记录 2017-12-12 + 在IIS下发布wordpress

工作记录 2017-12-12 序号 工作 相关人员 1 修改邮件上的问题。 更新RD服务器。 在IIS下发布wordpress。 郝 服务器更新 RD服务器更新了&#xff0c;更新的文件放在190的D:\Temp\CHTeam\fnehr_update_20171212\下了。 数据库更新: 数据库没有更新 更新的文件&#xf…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】1.2 安装与配置PostgreSQL(Windows/Linux/macOS)

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 1.2 安装与配置 PostgreSQL(Windows/Linux/macOS)1.2.1 操作系统兼容性与硬件要求1.2.2 Windows 安装与配置1.2.2.1 安装步骤1.2.2.2 服务管理1.2.2.3 配置文件路径1.2.3 Linux 安装与配置(以 Ubuntu…

epub格式转txt格式工具,txt批量转PDF

epub格式转txt格式工具&#xff0c;功能如图&#xff1a; txt格式批量转PDF 参考原文&#xff1a;epub格式转txt格式工具&#xff0c;txt批量转PDF 轻轻一点就关注, 好运连连挡不住&#xff0c;点个关注吧。

56.[前端开发-前端工程化]Day03-webpack构建工具

邂逅Webpack和打包过程 1 认识webpack工具 前端开发的流程 内置模块path path常见的API 在webpack中的使用 认识webpack 脚手架依赖webpack Webpack到底是什么呢 Webpack官方的图片 Vue项目加载的文件有哪些呢&#xff1f; Webpack的使用前提 Webpack的安装 2 webpack基本打包…

Rockchip Android平台打开GKI无法开机问题

Rockchip Android平台打开GKI无法开机问题 问题描述 Rockchip Android平台由于编译环境对pahole版本有要求&#xff0c;如果版本不对会导致ko无法加载从而导致系统无法开机。pahole的版本具体要求如下&#xff1a; Android版本pahole版本Android12/13版本pahole v1.21Androi…

SQL命令二:SQL 高级查询与特殊算法

引言 在掌握了 SQL 的基础操作和建表约束后&#xff0c;我们可以进一步探索 SQL 的高级查询功能和一些特殊算法。这些高级技巧能够帮助我们更高效地处理和分析数据&#xff0c;满足复杂的业务需求。 一、查询进阶 &#xff08;一&#xff09;简单查询 简单查询通过 select 语…

HTML04:图像标签

图像标签 常见的图像标签 JPGGIFPNGBMP <img src"路径" alt"名称" title"悬停名称" width"高" height"宽"/><!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&quo…

Docker —— 技术架构的演进

Docker —— 技术架构的演进 技术架构演进总结单机架构优点缺点总结 应用数据分离架构优点缺点总结 应用服务集群架构1. Nginx2. HAProxy3. LVS&#xff08;Linux Virtual Server&#xff09;4. F5 BIG-IP对比总结选型建议 读写分离/主从分离架构1. MyCat简介 2. TDDL&#xff…

[machine learning] Transformer - Attention (一)

Attention是Transformer的核心&#xff0c;本系列先通过介绍Attention来学习Transformer。本文先介绍简单版的Attention。 在Attention出现之前&#xff0c;通常使用recurrent neural networds (RNNs)来处理长序列数据。模型架构上&#xff0c;又通常使用encoder-decoder的结构…

Android 输入控件事件使用示例

一 前端 <EditTextandroid:id="@+id/editTextText2"android:layout_width="match_parent"android:layout_height="wrap_content"android:ems="10"android:inputType="text"android:text="Name" />二 后台代…

【向量数据库】用披萨点餐解释向量数据库:一个美味的技术类比

文章目录 前言场景设定&#xff1a;披萨特征向量化顾客到来&#xff1a;生成查询向量相似度计算实战1. 欧氏距离计算&#xff08;值越小越相似&#xff09;2. 余弦相似度计算&#xff08;值越大越相似&#xff09; 关键发现&#xff1a;度量选择影响结果现实启示结语 前言 想象…

人工智能和机器学习在包装仿真中的应用与价值

引言 随着包装成为消费品关键的差异化因素&#xff0c;对智能设计、可持续性和高性能的要求比以往任何时候都更高 。为了满足这些复杂的期望&#xff0c;公司越来越多地采用先进的仿真方法&#xff0c;而现在人工智能 (AI) 和机器学习 (ML) 又极大地增强了这些方法 。本文探讨…

【人工智能】深入探索Python中的自然语言理解:实现实体识别系统

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 自然语言理解(NLU)是人工智能(AI)领域中的重要研究方向之一,其目标是让计算机理解和处理人类语言。在NLU的众多应用中,实体识别(Nam…

个人健康中枢的多元化AI硬件革新与精准健康路径探析

在医疗信息化领域,个人健康中枢正经历着一场由硬件技术革新驱动的深刻变革。随着可穿戴设备、传感器技术和人工智能算法的快速发展,新一代健康监测硬件能够采集前所未有的多维度生物数据,并通过智能分析提供精准的健康建议。本文将深入探讨构成个人健康中枢的最新硬件技术,…