一、查找功能基本思路
通过实现一个基于倒排索引的搜索引擎,来提供高效、准确的搜索服务。其核心在于快速准确地从大量文档中检索出与用户查询关键词相关的文档,并按照相关性对结果进行排序。其中分为五个步骤:倒排索引的构建、分词处理、搜索查询处理、结果排序、结果格式化输出。
- 倒排索引的构建:搜索引擎首先需要构建一个倒排索引,这是一种将文档中出现的每个单词与包含该单词的文档列表相关联的数据结构。这样,当用户发起搜索请求时,搜索引擎可以迅速找到包含特定关键词的所有文档。
- 分词处理:由于搜索引擎通常需要处理自然语言文本,因此对用户输入的查询字符串进行分词是必要的步骤。这样可以帮助搜索引擎更准确地匹配文档中的关键词。
- 搜索查询处理:用户发起的搜索请求会被处理成一系列的关键词查询。搜索引擎会利用倒排索引来查找每个关键词相关的文档,并根据一定的算法(如权重累加)合并这些结果。
- 结果排序:为了提供最相关的搜索结果,搜索引擎会对查询结果进行排序。通常,这会根据文档与查询关键词的相关性(如关键词出现的频率、位置等因素)来确定排序。
- 结果格式化输出:最后,搜索引擎会将排序后的搜索结果格式化为易于用户理解的形式,如JSON格式,这样用户或其他应用程序就可以方便地读取和使用这些结果。
通过这种方式,搜索引擎能够快速响应用户的查询请求,提供相关性强、准确度高的搜索结果,从而提升用户体验。
二、详细代码
⭕searcher.hpp
#pragma once // 引入所需的头文件 #include "index.hpp" // 索引库 #include "util.hpp" // 工具库 #include "log.hpp" // 日志库 #include <algorithm> // C++ 标准库算法 #include <unordered_map> // 哈希表容器 #include <jsoncpp/json/json.h> // JSON处理库 // 定义命名空间ns_searcher namespace ns_searcher { // 定义用于打印的倒排元素结构体 struct InvertedElemPrint { uint64_t doc_id; // 文档ID int weight; // 权重 std::vector<std::string> words; // 包含的关键字 InvertedElemPrint() : doc_id(0), weight(0) {} // 默认构造函数 }; // 定义搜索引擎类Searcher class Searcher { private: // 索引对象指针 ns_index::Index *index; public: // 构造函数 Searcher() {} // 析构函数 ~Searcher() {} // 初始化搜索引擎 void InitSearcher(const std::string &input) { // 获取或创建索引对象 index = ns_index::Index::GetInstance(); LOG(NORMAL, "获取index单例成功..."); // 根据索引对象建立索引 index->BuildIndex(input); LOG(NORMAL, "建立正排和倒排索引成功..."); } // 执行搜索查询,query: 搜索关键字json_string: 返回给用户浏览器的搜索结果 void Search(const std::string &query, std::string *json_string) { // 对查询关键字进行分词 std::vector<std::string> words; ns_util::JiebaUtil::CutString(query, &words); // 准备用于存储搜索结果的结构 std::unordered_map<uint64_t, InvertedElemPrint> tokens_map; std::vector<InvertedElemPrint> inverted_list_all; // 遍历分词结果,查询索引并合并结果 for (std::string word : words) { boost::to_lower(word); // 转换为小写 ns_index::InvertedList *inverted_list = index->GetInvertedList(word); if (inverted_list) { for (const auto &elem : *inverted_list) { auto &item = tokens_map[elem.doc_id]; // 获取或创建对应doc_id的InvertedElemPrint item.doc_id = elem.doc_id; item.weight += elem.weight; item.words.push_back(elem.word); } } } // 将合并后的结果添加到inverted_list_all for (const auto &item : tokens_map) { inverted_list_all.push_back(std::move(item.second)); } // 根据权重降序排序搜索结果 std::sort(inverted_list_all.begin(), inverted_list_all.end(), [](const InvertedElemPrint &e1, const InvertedElemPrint &e2) { return e1.weight > e2.weight; }); // 构建JSON格式的搜索结果 Json::Value root; for (auto &item : inverted_list_all) { ns_index::DocInfo *doc = index->GetForwardIndex(item.doc_id); if (doc) { Json::Value elem; elem["title"] = doc->title;// 文档标题 elem["desc"] = GetDesc(doc->content, item.words[0]); // 获取描述 elem["url"] = doc->url; // 文档URL elem["id"] = (int)item.doc_id; // 文档ID elem["weight"] = item.weight; // 权重 root.append(elem); } } // 将JSON对象转换为字符串 Json::FastWriter writer; *json_string = writer.write(root); } // 获取文档描述的辅助函数 std::string GetDesc(const std::string &html_content, const std::string &word) { // 查找word在html_content中的首次出现位置 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); // 确定描述的起始和结束位置 int start = pos - 50; int end = pos + 100; if (start < 0) start = 0; if (end > (int)html_content.size()) end = (int)html_content.size(); // 截取描述内容并返回 std::string desc = html_content.substr(start, end - start); desc += "..."; return desc; } }; }
三、代码介绍
- 倒排元素结构体:在
ns_searcher
命名空间内部,定义了一个结构体InvertedElemPrint
,用于存储文档ID、权重和包含的关键字。这个结构体用于打印和展示搜索结果中的倒排列表元素。 - 搜索引擎类:定义了一个名为
Searcher
的类,它是搜索引擎的核心。这个类包含一个指向索引对象的指针,该索引对象用于存储和管理文档数据。 - 构造函数和析构函数:
Searcher
类有默认的构造函数和析构函数,用于初始化和销毁对象。 - 初始化搜索引擎:
InitSearcher
方法用于获取或创建索引对象,并根据该索引对象建立正排和倒排索引。这是搜索引擎工作的前提。 - 执行搜索查询:
Search
方法是搜索引擎的核心功能,它接收一个查询字符串,对该字符串进行分词处理,然后查询索引并合并搜索结果。搜索结果根据权重进行排序,并构建成JSON格式返回给用户。 - 分词处理:使用
ns_util::JiebaUtil::CutString
方法对查询字符串进行分词,这是中文文本处理中常见的步骤,以便更好地匹配文档中的关键词。 - 倒排索引查询:对于分词后的每个关键词,通过
index->GetInvertedList
方法查询倒排列表,并将结果合并到一个哈希表中。 - 结果排序和构建:合并后的搜索结果被添加到一个向量中,并根据权重进行降序排序。之后,构建JSON格式的搜索结果,包括文档标题、描述、URL和文档ID等信息。
- 获取文档描述的辅助函数:
GetDesc
方法用于从HTML内容中提取包含特定关键词的描述性文本片段。它查找关键词在内容中的首次出现位置,并截取前后各50个字符作为描述内容。
四、运行结果
🥰如下图所示在搜索引擎的查询结果中,我们成功地提取了关键信息,包括文档的标题、摘要和URL。
✅这些信息为用户提供了一个直观的概览,使得用户能够快速识别和访问他们感兴趣的具体内容。标题作为文档的主题概述,摘要提供了内容的简短描述,而URL则直接指向了原始资源的网络地址。通过这种方式,用户可以高效地从搜索结果中筛选和导航到他们寻求的信息。