【项目日记(三)】搜索引擎-搜索模块

news2025/1/12 6:52:18

❣博主主页: 33的博客❣
▶️文章专栏分类:项目日记◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多项目内容

在这里插入图片描述

目录

  • 1.前言
  • 2.项目回顾
  • 3.搜索流程
    • 3.1分词
    • 3.2触发
    • 3.3去重
    • 3.4排序
    • 3.5包装
  • 4.总结

1.前言

在前面的文章中,我们已经完成了索引的制作,既然已经制作好了索引,我们该如何更具输入内容,匹配对应的结果呢?接下来我们就一起完成搜索模块。

2.项目回顾

到目前为止,我们已经实现了2个类,Parser和Index。
实现Parser类:
1.通过递归枚举出所有的HTML文件。
2.针对每一个HTML进行解析操作。
a)标题:直接使用文件名称
b)URL:基于文件路径进行简单的字符串拼接
c)正文:去掉script和html标签
3.把解析内容通过addDoc放入Index类中

实现Index类:
正排索引:ArrayList
倒排索引:HashMap<String,ArrayList>
1.查正排:直接按照下标来取ArrayList中的元素
2.查倒排:直接按照Key,来区HashMap中的元素
3.添加文档,供Parser类调用
a)构建正排索引,构造DocInfo对象,添加到索引末尾
b)构建倒排索引,先对标题,正文进行分词操作,统计词频,添加到Map中去
4.保存索引:基于json格式把索引数据保存到指定文件中。
5.加载索引:基于json格式对数据进行解析,存入内存。

3.搜索流程

  • 1.【分词】根据输入内容进行分词操作
  • 2.【触发】针对分词结果来查倒排
  • 3.【去重】针对相同的文档进行去重
  • 4.【排序】针对去重结果按照权重排序
  • 5.【包装】针对排序结果查正牌,包装为Result进行返回数据

3.1分词

在使用Ansj技术进行分词操作的时候,会把空格,以及一些高频词例如a,an,is 等词语都分出来,但这些词语和我们的查询内容关联性并不大,我们就单独罗列出来,进行排除。网上有许多暂停词表可以自行下载,例如:
在这里插入图片描述

private static  String STOP_WORD_PATH="D:/doc_searcher_index/stop_word.txt";
private HashSet<String> stopWords=new HashSet<>();
public DocSearcher(){
        index.load();
        loadStopWords();
    }
public void loadStopWords(){
        try (BufferedReader bufferedReader=new BufferedReader(new FileReader(STOP_WORD_PATH))){
           while (true){
               String line=bufferedReader.readLine();
               if (line==null){
                   break;
               }
               stopWords.add(line);
           }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }    
List<Term> oldTerms=ToAnalysis.parse(query).getTerms();
        List<Term> terms=new ArrayList<>();
        for (Term term:oldTerms){
            if (stopWords.contains(term.getName())){
                continue;
            }
            terms.add(term);
        }          

3.2触发

 List<List<Weight>> termResult=new ArrayList<>();
        for (Term term:terms){
            String word=term.getName();
            List<Weight> invertedList=index.getInverted(word);
            if (invertedList==null){
                continue;
            }
            termResult.add(invertedList);
        }

3.3去重

前面,我们对用户输入的结果进行触发操作的时候,一个词可能出现在多个文档中,同理,一个文档也可能存在多个分词结果,如果我们不对相同的文档进行去重,那么一个文档针对不同的分词结果就会出现多次,这样显然不合理的。索引我们需要对相同的文档进行去重。那么具体该如何操作呢?触发的结果是一个二维数组,可以利用两个有序数组排序的思想进行去重,只不过这里运用的是多个有序数组排序。

  • 1.针对每一行按照升序排序
  • 2.借助优先级队列,争对多个有序数组进行合并
  • 3.初始化队列,把每一行第一个元素放入队列
  • 4.循环的取每行首个元素,遇到相同的DocId,权重相加
 List<Weight> allTermResult=mergeResult(termResult);
  static class Pos{
        public int row;
        public int col;
        public Pos(int row, int col) {
            this.row = row;
            this.col = col;
        }
    }
    private List<Weight> mergeResult(List<List<Weight>> source) {
    //1.针对每一行按照升序排序
        for (List<Weight> curRow:source){
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId()-o2.getDocId();
                }
            });
        }
    //2.借助优先级队列,争对多个有序数组进行合并
        List<Weight> target=new ArrayList<>();
      PriorityQueue<Pos> queue=new PriorityQueue<>(new Comparator<Pos>() {
          @Override
          public int compare(Pos o1, Pos o2) {
              Weight w1=source.get(o1.row).get(o1.col);
              Weight w2=source.get(o2.row).get(o2.col);
              return w1.getDocId()-w2.getDocId();
          }
      });
      //3.初始化队列,把每一行第一个元素放入队列
        for (int row=0;row<source.size();row++){
            queue.offer(new Pos(row,0));
        }
      //循环的取每行首个元素
      while (!queue.isEmpty()){
          Pos minPos=queue.poll();
          Weight curWeight=source.get(minPos.row).get(minPos.col);
          if (target.size()>0){
              Weight lastWeight=target.get(target.size()-1);
              //遇到相同的文章,权重相加
              if (lastWeight.getDocId()==curWeight.getDocId()){
                  lastWeight.setWeight(lastWeight.getWeight()+curWeight.getWeight());
              }else {
                  target.add(curWeight);
              }
          }else {
              target.add(curWeight);
          }
          Pos newPos=new Pos(minPos.row,minPos.col+1);
          if (newPos.col>=source.get(newPos.row).size()){
              continue;
          }
          queue.offer(newPos);
      }
      return target;
    }

3.4排序

  allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                //按照降序排序
                return o2.getWeight()-o1.getWeight();
            }
        });

3.5包装

需要注意的是返回的结果为标题,URL,描述,而描述不能直接把正文返回,而是返回一段包含用户分词结果的一小段描述。生成描述的思路:可以回去到所有分词结果,遍历分词结果,看哪个结果在正文中出现,那么直接截取分词的前10个字符和后160个字符来进行描述。

public class Result {
    private String title;
    private String url;
    private String desc;
    @Override
    public String toString() {
        return "Result{" +
                "title='" + title + '\'' +
                ", url='" + url + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
}

List<Result> results=new ArrayList<>();
        for (Weight weight:allTermResult){
            DocInfo docInfo=index.getDocInfo(weight.getDocId());
            Result result=new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            result.setDesc(GenDesc(docInfo.getContent(),terms));
            results.add(result);
        }
private String GenDesc(String content, List<Term> terms) {
        int firstPos=-1;
        for (Term term:terms){
            String word=term.getName();
            //避免出现word前后带有标点符号
            content=content.toLowerCase().replaceAll("\\b"+word+"\\b"," "+word+" ");
            firstPos=content.indexOf(" "+word+" ");
            if (firstPos>=0){
                break;
            }
        }
        if (firstPos==-1){
            if (content.length()>160){
                return content.substring(0,160)+"...";
            }
            return content;
        }
        String desc="";
        int descBeg=firstPos<60?0:firstPos-60;
        if (descBeg+160>content.length()){
            desc=content.substring(descBeg);
        }else {
            desc=content.substring(descBeg,descBeg+160)+"...";
        }
        //把描述中和分词结果相同的部分设置为斜体加上<i>标签,方便前端标红
        for (Term term:terms){
            String word=term.getName();
            //进行忽略大小写的全词匹配
            desc=desc.replaceAll("(?i) "+word+" ","<i> "+word+" </i>");
        }
        return desc;
    }

4.总结

这篇文章主要介绍了,搜索引擎的搜锁模块,这部分的难点主要是去重操作,去重的时候需要用到我们之前学过的数据结构,小根堆结合多个有序数组完成去重操作!

下期预告:搜索引擎(四)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1888951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全网新鲜出炉的Stable Diffusion 人物发型提示词大全,中英文列表!

前言 简介&#xff1a; 使用发型提示词能更精确描述所需图像的发型特征&#xff0c;如卷发、短发、颜色和风格。结合正负提示词&#xff0c;确保生成图片符合预期。尝试使用工具如PromptChoose来创建个性化图像描述&#xff0c;包含多种发型选项&#xff0c;如刘海、马尾、波浪…

6.5、函数的常见形式

代码 #include <iostream> using namespace std; #include <string>//函数的的常见延时 //1、无参无反 void test01() {cout << "this is test01" << endl; } //2、有参无反 void test02(int a) {cout << "this is test02 a &q…

QT学习积累——方法参数加const和不加const的区别

目录 引出方法参数加const和不加const的区别方法加static和不加static的区别Qt遍历list提高效率显示函数的调用使用&与不使用&除法的一个坑 总结自定义信号和槽1.自定义信号2.自定义槽3.建立连接4.进行触发 自定义信号重载带参数的按钮触发信号触发信号拓展 lambda表达…

【探索Linux】P.36(传输层 —— TCP协议段格式)

阅读导航 引言一、TCP段的基本格式二、控制位详细介绍三、16位接收窗口大小⭕窗口大小的作用⭕窗口大小的限制⭕窗口缩放选项⭕窗口大小的更新⭕窗口大小与拥塞控制 四、紧急指针温馨提示 引言 在上一篇文章中&#xff0c;我们深入探讨了一种无连接的UDP协议&#xff0c;它以其…

【数据结构】04.双向链表

一、双向链表的结构 注意&#xff1a;这里的“带头”跟前面我们说的“头节点”是两个概念&#xff0c;带头链表里的头节点&#xff0c;实际为“哨兵位”&#xff0c;哨兵位节点不存储任何有效元素&#xff0c;只是站在这里“放哨的”。 “哨兵位”存在的意义&#xff1a;遍历循…

以太坊DApp交易量激增83%的背后原因解析

引言 最近&#xff0c;以太坊网络上的去中心化应用程序&#xff08;DApp&#xff09;交易量激增83%&#xff0c;引发了广泛关注和讨论。尽管交易费用高达2.4美元&#xff0c;但以太坊仍在DApp交易量方面遥遥领先于其他区块链网络。本文将深入探讨导致这一现象的主要原因&#…

Redux 使用及基本原理

什么是Redux Redux 是用于js应用的状态管理库&#xff0c;通常和React一起用。帮助开发者管理应用中各个组件之间的状态&#xff0c;使得状态的变化变得更加可预测和易于调试。 Redu也可以不和React组合使用。&#xff08;通常一起使用&#xff09; Redux 三大原则 单一数据源…

图书馆书籍管理系统

项目名称与项目简介 图书馆书籍管理系统 本项目是一个计算机管理系统&#xff0c;也就是将传统手工的管理方式转变为智能化、标准化、规范化的管理管理模式&#xff0c;对图书馆中所有的图书、文献资料、音像资料、报刊、期刊等各种类型的资料实现采编、收集图书信息、检索、归…

【笔记】强化学习,gym的命令行图形化界面适配

搞了一大堆还是搞不出来放弃了 最后用matplotlib画出来看 import gym import matplotlib.pyplot as plt from IPython import display import numpy as np %matplotlib inlineenv gym.make(CartPole-v1, render_mode"rgb_array") observation env.reset() a 0 f…

JWT入门

JWT与TOKEN JWT&#xff08;JSON Web Token&#xff09;是一种基于 JSON 格式的轻量级安全令牌&#xff0c;通常用于在网络应用间安全地传递信息。而“token”一词则是一个更广泛的术语&#xff0c;用来指代任何形式的令牌&#xff0c;用于在计算机系统中进行身份验证或授权。J…

EIOT能源物联网平台在连锁门店的应用

在当今快节奏的商业环境中&#xff0c;连锁门店的管理和运营变得越来越具有挑战性。能源数据是连锁门店的管理中重要组成部分&#xff0c;为了提高门店的能源利用效率和管理水平&#xff0c;需要依赖先进的集团能源管理系统&#xff0c;进而实现节能减排&#xff0c;优化运营成…

探索指针(3)-C语言

目录 1.字符指针变量 一. 什么是字符指针&#xff1f; 二.字符指针的使用 三.常量字符串与字符指针的关系 四.字符数组和字符串常量 2.数组指针变量 一. 指向数组的指针 3. 二维数组传参的本质 指针形式的函数参数 4.函数指针变量 一. 函数指针的声明 二. 初始化和…

常见锁策略之可重入锁VS不可重入锁

可重入锁VS不可重入锁 有一个线程,针对同一把锁,连续加锁两次,如果产生了死锁,那就是不可重入锁,如果没有产生死锁,那就是可重入锁. 死锁 我们之前引入多线程的时候不是讲了一个加数字的案例么,我们今天以它来举例 当我们这样写的时候会出现什么问题? 分析:第一个synchron…

alibaba EasyExcel 简单导出数据到Excel

导入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.1</version> </dependency> 1、alibaba.excel.EasyExcel导出工具类 import com.alibaba.excel.EasyExcel; import …

揭秘,PyArmor库让你的Python代码更安全

PyArmor 概述: PyArmor 是一个用于加密和保护 Python 源代码的工具,旨在防止代码被逆向工程和未经授权的使用.通过将 Python 源代码编译为加密的字节码,PyArmor 提供了一种有效的方法来保护知识产权和敏感算法. 安装 pip install pyarmor安装完成后,可以通过以下命令验证安装…

SaaS联盟分销系统如何高效管理推广渠道的实用指南

随着众多SaaS企业不断向PLG模式转型&#xff0c;传统的推广方式&#xff0c;比如广告投放推流、第三方cookie数据追踪等方式的成本效益比低下&#xff0c;更多的SaaS企业选择可“低成本&#xff0c;高回报”的联盟营销策略。比如Figama、Unbounce、Looka这些中小型SaaS企业&…

SCI丨5分期刊,JCR一区

SCI&#xff0c;5分&#xff0c;JCR Q1&#xff0c;中科大类3小类2区 1 基于复杂网络与xxx能源汽车节能数值分析 2 基于热能损失优化的xxx与性能管理 3 基于xxxLCA技术的绿色制造工艺优化研究 4 基于xxx入侵检测技术的物联网智能制造监控系统设计 6 基于物联网技术xxx电力系…

鸿蒙认证值得考吗?

鸿蒙认证值得考吗&#xff1f; 鸿蒙认证&#xff08;HarmonyOS Certification&#xff09;是华为为了培养和认证开发者在鸿蒙操作系统&#xff08;HarmonyOS&#xff09;领域的专业技能而设立的一系列认证项目。这些认证旨在帮助开发者和企业工程师提升在鸿蒙生态中的专业技能…

通过混合栅极技术改善p-GaN功率HEMTs的ESD性能

来源&#xff1a;Improved Gate ESD Behaviors of p-GaN PowerHEMTs by Hybrid Gate Technology&#xff08;ISPSD 24年&#xff09; 摘要 本工作中&#xff0c;首次证明了混合栅极技术在不增加额外面积和寄生效应的前提下&#xff0c;能有效提升p-GaN HEMTs的栅极静电放电(E…

刷代码随想录有感(124):动态规划——最长公共子序列

题干&#xff1a; 代码&#xff1a; class Solution { public:int findLength(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>>dp(nums1.size() 1, vector<int>(nums2.size() 1, 0));int res 0;for(int i 1; i <…