项目的相关背景
- 站内搜索:搜索的数据更垂直(内容相关性高),数据量更小
- boost站内是没有搜索引擎的
搜索引擎的原理
搜索引擎技术栈和项目环境
-
技术栈: c/c++ c++11 STL 准标准库boost jsoncpp cppjieba,cpp-httplib
-
项目环境: centos7 服务器 vim/gcc/g++ /Makefile,vs
-
boost开发库的安装sudo yum install -y boost-devel //boost的开发库 搜用的是1.53 使用的文档是最新的那个
-
sudo yum install -y jsoncpp-devel
工具 util.hpp
namespace ns_util
{
class FileUtil
{
public:
static bool ReadFile(const std::string &file_path, std::string *out)
{
std::ifstream in(file_path, std::ios::in); // 读文件
if (!in.is_open())
{
std::cerr << "open file" << file_path << "error" << std::endl;
return false;
}
std::string line;
// 为什么getline的返回值是流的引用&,while是bool,能进行判断呢?
// 重载了强制类型转换
while (std::getline(in, line))
{
*out += line;
}
in.close();
return true;
}
};
class StringUtil
{
public:
// 字符串切割
static void Split(const std::string &target, std::vector<std::string> *out, std::string sep)
{
// boost::token_compress_of off:压缩关闭
// boost::token_compress_on on:压缩打开 --将所有相连的分隔符压缩成一个aa\3\3\3\3\3bbb-->aa bbb
boost::split(*out, target, boost::is_any_of(sep), boost::token_compress_on);
}
};
// dict路径
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";
class JiebaUtil
{
private:
// static cppjieba::Jieba jieba;
cppjieba::Jieba jieba;
std::unordered_map<std::string, bool> stop_words;
private:
JiebaUtil()
: jieba(DICT_PATH, HMM_PATH, USER_DICT_PATH, IDF_PATH, STOP_WORD_PATH)
{}
JiebaUtil(const JiebaUtil &) = delete;
static JiebaUtil *instance;
public:
static JiebaUtil *get_instance()
{
static std::mutex mtx;
if (nullptr == instance)
{
mtx.lock();
if (nullptr == instance)
{
instance = new JiebaUtil();
instance->InitJiebaUtil();
}
mtx.unlock();
}
return instance;
}
// 把暂停词加载进来
void InitJiebaUtil()
{
std::ifstream in(STOP_WORD_PATH);
if (!in.is_open())
{
LOG(FATAL, "load stop words file error");
return;
}
std::string line;
while (std::getline(in, line))
{
stop_words.insert({line, true});
}
in.close();
}
void CutStringHelper(const std::string &src, std::vector<std::string> *out)
{
jieba.CutForSearch(src, *out);
for (auto iter = out->begin(); iter != out->end();)
{
auto it = stop_words.find(*iter);
if (it != stop_words.end())
{
// 说明当前的string 是暂停词,需要去掉
iter = out->erase(iter);
}
else
{
iter++;
}
}
}
public:
static void CutString(const std::string &src, std::vector<std::string> *out)
{
ns_util::JiebaUtil::get_instance()->CutStringHelper(src, out);
// jieba.CutForSearch(src,*out);
}
};
JiebaUtil *JiebaUtil::instance = nullptr;
// cppjieba::Jieba JiebaUtil::jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);
}
正排索引 和倒排索引
- 文档1:张三在吃饭
- 文档2:张三在买东西
正排索引:从文档ID找到文档内容(文档内的关键字)
文档ID | 文档内容 |
---|---|
1 | 张三在吃饭 |
2 | 张三在买东西 |
目标文档进行分词(目的:方便建立倒排序索引和查找)
暂停词:了,的,吗,a,the…在分词的时候不考虑
- 张三在吃饭 : 张三/吃饭
- 张三在买东西:张三/买东西
倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应联系到文档ID的方案
关键字(具有唯一性) | 文档ID,weight(权重) |
---|---|
张三 | 文档1,文档2 |
吃饭 | 文档1 |
买东西 | 文档2 |
查找过程: 用户输入张三-> 倒排索引->提取文档ID(1,2)->根据正排索引->找到文档的内容 ->title +conent(desc)+url 文档结果进行摘要 ->构建响应结果
Index.hpp --索引模块
正排索引
//DocInfo:描述文档内容
struct DocInfo
{
std::string title; //文档的标题
std::string content; //文档对应的去标签之后的内容
std::string url; //官网文档url
uint64_t doc_id; //文档的ID,用uint64_t:为了防止索引越界
};
std::vector<DocInfo> forward_index; // 正排拉链
DocInfo* GetForwardIndex(uint64_t doc_id)
{
if(doc_id >= forward_index.size()) //没有该文档id
{
std::cerr << "doc_id out range, error!" << std::endl;
return nullptr;
}
return &forward_index[doc_id]; //数组的下标天然就是文档id,所以直接返回数组对应的内容的地址
}
DocInfo *BuildForwardIndex(const std::string &line) //建立正排索引
{
//1. 解析line->本质是进行字符串切分,解析成:title, content, url
std::vector<std::string> results;//保存的是切分好内容
const std::string sep = "\3"; //分隔符
ns_util::StringUtil::Split(line, &results, sep);
if(results.size() != 3)
{
return nullptr;
}
//2. 切分好的字符串进行填充到DocIinfo(正排索引节点)
DocInfo doc;
doc.title = results[0]; //title
doc.content = results[1]; //content
doc.url = results[2]; ///url
//此时的先进行保存id,然后再插入,此时对应的id就是当前doc在vector中的下标
doc.doc_id = forward_index.size();
//3. 插入到正排索引数组当中
forward_index.push_back(std::move(doc));
return &forward_index.back(); //返回这个新插入的内容,描述这个文档内容的正排索引节点
}
倒排索引
struct InvertedElem
{
uint64_t doc_id;//对应的文档id
std::string word;//关键字
int weight; //权重 可以根据权重决定文档显示的先后顺序
InvertedElem()
:weight(0){}
};
typedef std::vector<InvertedElem> InvertedList;// 倒排拉链
std::unordered_map<std::string, InvertedList> inverted_index; //存放关键字和倒排拉链的映射关系
//根据关键字word,获得倒排拉链
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); //InvertedList
}
bool BuildInvertedIndex(const DocInfo &doc)//建立倒排索引,参数是正排索引节点
{
//DocInfo里面包含一个html文件的:{title, content, url, doc_id}
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 15 //标题当中出现的词,权重更高
#define Y 1
for(auto &word_pair : word_map)
{
InvertedElem item;//构建倒排索引结构节点
item.doc_id = doc.doc_id;
item.word = word_pair.first;//unordered_map<std::string, word_cnt> word_map
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;
}
把boost库导入进来
rz - E 传输boost_最新_.tar.gz 文件到linux中
tar xzf boost_最新_.tar.gz 解压
/home/jiantao/boost_search/boost_1_83_0/doc/html 文档所在的路径
mkdir -p data/input 作为数据源boost库的html文件都在这里
cp -rf boost_1_83_0/doc/html/* data/input/ 用html文件建立索引
touch parser.cc
Parser.cc–清洗模块
jiantao jiantao 16384 Oct 4 10:03 input //原始文档
jiantao jiantao 0 Oct 4 10:09 parser.cc
jiantao jiantao 4096 Oct 4 10:13 raw_html //去标签之后的干净文档
[jiantao@VM-4-2-centos data]$ ls -Rl | grep -E '*.html' | wc -l
8583
1.递归式的把.html文件保存 下来
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++)
{
// 判断是不是普通文件
if (!fs::is_regular_file(*iter))
{
continue;
}
// 判断文件名的后缀是否符合要求
if (iter->path().extension() != ".html")
{
continue;
}
files_list->push_back(iter->path().string()); // 把带路径的html保存在files_list中
}
return true;
}
2.解析html,处理结果保存在result中
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 (!ns_util::FileUtil::ReadFile(file, &result))
{
continue;
}
DocInfo_t doc;
// 解析文件,提取title
if (!ParseTitle(result, &doc.title))
{
continue;
}
// 解析文件,提取continue--去标签
if (!ParseContent(result, &doc.content))
{
continue;
}
// 解析文件路径,构建url
if (!ParseUrl(file, &doc.url))
{
continue;
}
// 完成解析任务,解析结果保存在doc中
results->push_back(std::move(doc)); // 减少拷贝
}
return true;
}
提取titile
// <title> xxxxxxxxxxxxxxxx </title>
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(); // begin指向<title>后面第一个字符
if (begin > end)
{
return false;
}
*title = file.substr(begin, end - begin);
return true;
}
去标签
在进行遍历的时候,只要碰到了> 就意味着当前标签被处理完毕碰到< 意味着标签开始了
<>:html 的标签,是没有意义需要去掉,标签一般的成对出现的
static bool ParseContent(const std::string &file, std::string *content)
{
// 去标签,状态机
enum status
{
LABLE,
CONTENT
};
enum status s = LABLE; // 默认为LABLE状态
for (char c : file) //以字符方式遍历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);
}
break;
default:
break;
}
}
return true;
}
3.构建URL
boost库的官方文档和下载下来的文档是有路径对应关系的
//官网URL
https://www.boost.org/doc/libs/1_83_0/doc/html/accumulators.html
//linux中URL
data/input/accumulators.html//下载下来的boost库doc/html/拷贝到data/input下
url_head ="https://boost.org/doc/libs/1_83_0/doc/html/"
url_tail=data/input/accumulators.html --> /accumulators.html
url = url_head + url_tail //相当于一个官网连接
目标:把每个文档去标签之后,然后写入到同一个文件中,每个文档内容不需要\n,
文档和文档之间用\3 区分(是控制字符不会显示出来污染)
#define SEP '\3'
//解析完的文件保存在output中
bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
std::ofstream out(output, std::ios::out | std::ios::binary); // 二进制写入--写入什么文档保存就是什么
if (!out.is_open())
{
std::cerr << "open " << output << "failed" << std::endl;
return false;
}
//文件内容写入
//title\3content\3url \n title\3content\3url \n ....
//方便getline(ifsream,line)直接获取文档全部内容 title\3content\3url
//(getline以\n结束)
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());
}
out.close();
return true;
}
http_server模块
gitee 搜索cpp-httplib
gcc -v 查看gcc版本
//安装scl源
sudo yum install centos-release-scl scl-utils-build
//安装新版本的gcc
sudo yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++
//启动新版本
//命令行启动只能在本会话有效
//vim ~/.bash_profile 可以写入这个文件中每次登录都是最新的
scl enable devtoolset-7 bash
#pragma once
#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include <unordered_map>
#include <jsoncpp/json/json.h>
#include "log.hpp"
namespace ns_searcher
{
struct InvertedElemPrint
{
uint64_t doc_id;//文档id
int weight;//累加权重
std::vector<std::string> words;//一个doc_id对应多个关键字
InvertedElemPrint():doc_id(0), weight(0)
{}
};
class Searcher
{
private:
ns_index::Index *index; //供系统进行查找的索引
public:
Searcher(){}
~Searcher(){}
//建立索引需要传入的是经过数据清洗之后的文件存放的位置 input其实就是:data/raw_html/raw.txt
void InitSearcher(const std::string &input) //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,"建立正排和倒排索引成功...");
}
//获取一部分摘要
std::string GetDesc(const std::string &html_content, const std::string &word)
{
//1.在html_content范围内,找word首次出现的位置
auto cmp = [](int x, int y){ return (std::tolower(x) == std::tolower(y)); };
auto iter = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), cmp);
if(iter == html_content.end())
{
return "None1";
}
int pos = iter - html_content.begin();
//2. 获取start,end的位置
int start = 0; //默认就是在文本的起始位置
int end = html_content.size() - 1;//默认是文本的结尾
//找到word在html_content中的首次出现的位置,然后往前找50字节,如果没有,就从begin开始
//往后找100字节如果没有,到end就可以的 ,然后截取出这部分内容
const int prev_step = 50;
const int next_step = 100;
if(pos - prev_step > 0)
start = pos - prev_step;
if(pos+next_step < end)
end = pos + next_step;
//3. 截取子串,然后return
if(start >= end)
return "None2";
//从start开始,截取end - start个字节的内容
std::string desc = html_content.substr(start, end - start);
desc += "...";
return desc;
}
//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索引查找
std::vector<InvertedElemPrint> inverted_list_all;//存放所有经过去重之后的倒排拉链
//根据doc_id进行去重,凡是id相同的倒排索引节点,其关键字都放在InvertedElemPrint的vector里面
std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;
//因为我们建立索引的时候,建立index是忽略大小写,所以搜索的时候关键字也需要忽略大小写
for(std::string word : words)
{
boost::to_lower(word);//将切分之后的查找关键字转为小写
//先查倒排
ns_index::InvertedList *inverted_list = index->GetInvertedList(word);//通过关键字获得倒排拉链
if(nullptr == inverted_list)//该关键字不存在倒排拉链
{
continue;//检测下一个关键词
}
for(const auto &elem : *inverted_list) //遍历倒排拉链,合并索引节点
{
//[]:如果存在直接获取,如果不存在新建
auto &item = tokens_map[elem.doc_id]; //根据文档id获得其对应的关键字集合
//此时item一定是doc_id相同的倒排拉链打印节点
item.doc_id = elem.doc_id;
item.weight += elem.weight;//权值累加
item.words.push_back(elem.word); //把当前的关键字放到去重的倒排拉链打印节点当中的vector当中保存
}
}
for(const auto &item : tokens_map)//遍历去重之后的map
{
inverted_list_all.push_back(std::move(item.second)); //插入倒排拉链打印节点到inverted_list_all
}
//3.汇总查找结果,按照相关性(weight)进行降序排序
auto cmp = [](const InvertedElemPrint &e1, const InvertedElemPrint &e2){
return e1.weight > e2.weight;
};
std::sort(inverted_list_all.begin(), inverted_list_all.end(),cmp);
//4.根据查找出来的结果,构建json串 -- jsoncpp --通过jsoncpp完成序列化&&反序列化
Json::Value root;
for(auto &item : inverted_list_all){
//item就是倒排索引打印节点
ns_index::DocInfo * doc = index->GetForwardIndex(item.doc_id);//根据文档id->查询正排,返回正排索引节点
if(nullptr == doc){
continue;
}
//doc里面就有title,url,content,文档id
//我们想给浏览器返回的是网页的title,内容摘要,链接
Json::Value elem;
elem["title"] = doc->title;
//item是经过去重之后的节点, 此时的words是个vector,直接使用words[0]获得摘要即可,0号下标的值一定存在
elem["desc"] = GetDesc(doc->content, item.words[0]);
elem["url"] = doc->url;
//for deubg, for delete
elem["id"] = (int)item.doc_id; //doc_id是uint64 ,json可能报错,所以转为int
elem["weight"] = item.weight; //doc_id,weight虽然是int,但是json会帮我们自动转为string
root.append(elem);
}
//Json::StyledWriter writer; //为了方便调试观看
Json::FastWriter writer;
*json_string = writer.write(root);
}
};
}
网页
网络有单标签,双标签
- html: --网页结构
- css:网页美观
- js(javascript) 动态效果,和前后端交互
vscode 配置 安装 open in browser
!+table 会自动补齐
安装cpp-httplib 0.7.15版本 拖到thirdpart目录下面
unzip cpp-httplib-v0.7.15.zip
ln -s ~/thirdpart/cpp-httplib-v0.7.15 cpp-httplib
wwwroot --网页信息
编写css
<style>
/* 去掉网页中的所有的默认内外边距,html的盒子模型 */
* {
/* 设置外边距 */
margin: 0;
/* 设置内边距 */
padding: 0;
}
/* 将我们的body内的内容100%和html的呈现吻合 */
html,
body {
height: 100%;
}
/* 类选择器.container */
.container {
/* 设置div的宽度 */
width: 800px;
/* 通过设置外边距达到居中对齐的目的 */
margin: 0px auto;
/* 设置外边距的上边距,保持元素和网页的上部距离 */
margin-top: 15px;
}
/* 复合选择器,选中container 下的 search */
.container .search {
/* 宽度与父标签保持一致 */
width: 100%;
/* 高度设置为52px */
height: 52px;
}
/* 先选中input标签, 直接设置标签的属性,先要选中, input:标签选择器*/
/* input在进行高度设置的时候,没有考虑边框的问题 */
.container .search input {
/* 设置left浮动 */
float: left;
width: 600px;
height: 50px;
/* 设置边框属性:边框的宽度,样式,颜色 */
border: 1px solid black;
/* 去掉input输入框的有边框 */
border-right: none;
/* 设置内边距,默认文字不要和左侧边框紧挨着 */
padding-left: 10px;
/* 设置input内部的字体的颜色和样式 */
color: #CCC;
font-size: 14px;
}
/* 先选中button标签, 直接设置标签的属性,先要选中, button:标签选择器*/
.container .search button {
/* 设置left浮动 */
float: left;
width: 150px;
height: 52px;
/* 设置button的背景颜色,#4e6ef2 */
background-color: #4e6ef2;
/* 设置button中的字体颜色 */
color: #FFF;
/* 设置字体的大小 */
font-size: 20px;
font-family: "lucida grande", "lucida sans unicode", lucida, helvetica, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
}
.container .result {
width: 100%;
}
.container .result .item {
margin-top: 15px;
}
.container .result .item a {
/* 设置为块级元素,单独站一行 */
display: block;
/* a标签的下划线去掉 */
text-decoration: none;
/* 设置a标签中的文字的字体大小 */
font-size: 20px;
/* 设置字体的颜色 */
color: #4e6ef2;
}
.container .result .item a:hover {
text-decoration: underline;
}
.container .result .item p {
margin-top: 5px;
font-size: 16px;
font-family: "lucida grande", "lucida sans unicode", lucida, helvetica, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
}
.container .result .item i {
/* 设置为块级元素,单独站一行 */
display: block;
/* 取消斜体风格 */
font-style: normal;
color: green;
}
</style>
编写jss
用原生js成本高,jQuery好
<script>
function Search(){
// 是浏览器的一个弹出框
//alert("hello js!");
// 1. 提取数据, $可以理解成就是JQuery的别称
let query = $(".container .search input").val();
console.log("query = " + query); //console是浏览器的对话框,可以用来进行查看js数据
//2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数,JQuery中的
$.ajax({
type: "GET",
url: "/s?word=" + query,
success: function(data){
console.log(data);
BuildHtml(data);
}
});
}
function BuildHtml(data){
// if(data==''||data==ull)
// {
// document.write("搜索不到");
// return;
// }
// 获取html中的result标签
let result_lable = $(".container .result");
// 清空历史搜索结果
result_lable.empty();
for( let elem of data){
let a_lable = $("<a>", {
text: elem.title,
href: elem.url,
// 跳转到新的页面
target: "_blank"
});
let p_lable = $("<p>", {
text: elem.desc
});
let i_lable = $("<i>", {
text: elem.url
});
let div_lable = $("<div>", {
class: "item"
});
a_lable.appendTo(div_lable);
p_lable.appendTo(div_lable);
i_lable.appendTo(div_lable);
div_lable.appendTo(result_lable);
}
}
</script>
log
#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;
}
去掉暂停词
cd dict ----里面有个stop_words.utf8 里面是暂停词
void InitJiebaUtil()
{
std::ifstream in(STOP_WORD_PATH);
if (!in.is_open())
{
LOG(FATAL, "load stop words file error");
return;
}
std::string line;
while (std::getline(in, line))
{
stop_words.insert({line, true});
}
in.close();
}
void CutStringHelper(const std::string &src, std::vector<std::string> *out)
{
jieba.CutForSearch(src, *out);
for (auto iter = out->begin(); iter != out->end();)
{
auto it = stop_words.find(*iter);
if (it != stop_words.end())
{
// 说明当前的string 是暂停词,需要去掉
iter = out->erase(iter);
}
else
{
iter++;
}
}
}
去掉暂停词
cd dict ----里面有个stop_words.utf8 里面是暂停词
void InitJiebaUtil()
{
std::ifstream in(STOP_WORD_PATH);
if (!in.is_open())
{
LOG(FATAL, "load stop words file error");
return;
}
std::string line;
while (std::getline(in, line))
{
stop_words.insert({line, true});
}
in.close();
}
void CutStringHelper(const std::string &src, std::vector<std::string> *out)
{
jieba.CutForSearch(src, *out);
for (auto iter = out->begin(); iter != out->end();)
{
auto it = stop_words.find(*iter);
if (it != stop_words.end())
{
// 说明当前的string 是暂停词,需要去掉
iter = out->erase(iter);
}
else
{
iter++;
}
}
}