boost搜索引擎

项目的相关背景

  • 站内搜索:搜索的数据更垂直(内容相关性高),数据量更小
  • 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++;
                }
            }
        }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值