基于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.pswp.cn/diannao/81878.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81878.shtml
英文地址,請注明出處:http://en.pswp.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硬件革新與精準健康路徑探析

在醫療信息化領域,個人健康中樞正經歷著一場由硬件技術革新驅動的深刻變革。隨著可穿戴設備、傳感器技術和人工智能算法的快速發展,新一代健康監測硬件能夠采集前所未有的多維度生物數據,并通過智能分析提供精準的健康建議。本文將深入探討構成個人健康中樞的最新硬件技術,…