建立倒排索引的时候要忽略大小写
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(std::string 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(std::string 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;
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;
}
编写搜索引擎模块Searcher
基本代码结构
搜索的关键字也要在服务端进行分词,然后,才能进行查找index
新建searcher.hpp
touch searcher.hpp
#include "index.hpp"
namespace ns_searcher{
class Searcher{
private:
ns_index::Index *index; //供系统进行查找的索引
public:
Searcher(){}
~Searcher(){}
public:
void InitSearcher(const std::string &input)
{
//1.获取或者创建index对象
//2.根据index对象建立索引
}
//query:搜索关键字
//json_string:返回给用户浏览器的搜索结果
void Search(const std::string &query, std::string *json_string)
{
//1.[分词]:对query进行按照searcher的要求进行分词
//2.[触发]:就是根据分词的各个词,进行对应的index搜索
//3.[合并排序]:根据汇总查找结果,按照相关性(weight)降序排序
//4.[构建]:根据对应的查找出来的结果,构建json串 -- jsoncpp
}
};
}
编写index单例
index.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>
#include <mutex>
#include "util.hpp"
namespace ns_index{
struct DocInfo{
std::string title; //文档标题
std::string content; //文档对应的去标签之后的内容
std::string url; //官网文档url
uint64_t dic_id; //文档的ID
}
struct InvertedElem{
uint64_t doc_id;
std::string word;
int weight;
}
//倒排拉链
typedef std::vector<InvertedElem> InvertedList;
class Index{
private:
//正排索引的数据结构用数组,数组的下标天然是文档的ID
std::vector<DocInfo> forward_index; //正排索引
//倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]
std::unordered_map<std::string, InvertedList> inverted_index;
private:
Index(){} //但是一定要有函数体,不能delete
Index(const Index&) = delete;
Index& operator = (const Index&) = delete;
static Index* instance;
static std::mutex mtx;
public:
~Index(){}
public:
static Index* GetInstance()
{
if(nullptr == instance){
mtx.lock();
if(nullptr == instance){
instance = new Index();
}
mtx.unlock();
return instance;
}
}
//根据doc_id找到文档内容
DocInfo *GetForwardIdex(uint64_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];
}
//根据关键字string获得倒排拉链
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);
}
//根据去标签,格式化之后的文档,构建正排和倒排索引
//data/raw_html/raw.txt
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;
while(std::getline(in, line)){
DocInfo * doc = BuildForwardIndex(line);
if(nullptr == doc){
std::cerr << "build " << line << " error" << std::endl; //for debug
continue;
}
BuildInvertedIndex(*doc);
}
return true;
}
private:
DocInfo *BuildForwardIndex(const std::string &line)
{
//1.解析line,字符串切分
//line -> 3 string, title, content, url
std::vector<std::string> results;
const std::string sep = '\3'; //行内分隔符
ns_util::StringUtil::CutString(line, &results, sep);
if(results.size() != 3){
return nullptr;
}
//2.字符串进行填充到DocInfo中
DocInfo doc;
doc.title = results[0]; //title
doc.content = results[1]; //content
doc.url = results[2]; //url
doc.doc_id = forward_index.size(); //先保存id,再插入,对应的id就是当前doc在vector中的下标
//3.插入到正排索引的vector中
forward_index.push_back(std::move(doc)); //doc.html文件内容
return &forward_index.back();
}
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(std::string 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(std::string 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;
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;
}
};
Index* Index::instance = nullptr;
}
searcher文件
#include "index.hpp"
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();
//2.根据index对象建立索引
index->BuildIndex(input);
}
//query:搜索关键字
//json_string:返回给用户浏览器的搜索结果
void Search(const std::string &query, std::string *json_string)
{
//1.[分词]:对query进行按照searcher的要求进行分词
//2.[触发]:就是根据分词的各个词,进行对应的index搜索
//3.[合并排序]:根据汇总查找结果,按照相关性(weight)降序排序
//4.[构建]:根据对应的查找出来的结果,构建json串 -- jsoncpp
}
};
}
编写查找代码
引入第三方库json
sudo yum install -y jsoncpp-devel
编写searcher文件
#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>
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();
//2.根据index对象建立索引
index->BuildIndex(input);
}
//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查找,建立index是忽略大小写,所以搜索,关键字也需要
ns_index::InvertedList inverted_list_all; //内部InvertedElem
for(std::string s : words){
boost::to_lower(word);
ns_index::InvertedList *inverted_list = index->GetInvertedList(word);
if(nullper == inverted_list){
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串 -- jsoncpp完成序列化和反序列化
Json::Value root;
for(auto &item : inverted_list_all){
ns_index::DocInfo * doc = index->GetForwardIndex(item.doc_id);
if(nullptr == doc){
continue;
}
Json::Value elem;
elem["title"] = doc->title;
elem["desc"] = doc->content; //content是文档的去标签的结果,但是不是我们要的,我们要一部分
elem["url"] = doc->url;
root.append(elem);
}
Json::StyledWriter writer;
*json_string = writer.write(root);
}
};
}
建立server.cc
touch server.cc
#include "searcher.hpp"
int main()
{
return 0;
}
可以进行搜索测试