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

❣博主主页: 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>() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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;@Overridepublic 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.mzph.cn/pingmian/39426.shtml

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

相关文章

虚拟机USB——解决每次插U盘都得选择连接到主机还是虚拟机问题

虚拟机USB——解决每次插U盘都得选择连接到主机还是虚拟机问题 1.编辑–>首选项–> 2.如果想每次插U盘都连接到主机就选“将设备连接到主机” 如果想每次插U盘都进行选择&#xff0c;就选择“询问要执行的操作”

在 Mac 上使用 本地 LLM 文本终结

我们可使用本地大型语言模型&#xff0c;如Mistral、Llama等&#xff0c;来给文本做总结&#xff0c;相比在线的 Kimi &#xff0c;ChatGPT&#xff0c; 我们不用担心数据泄露&#xff0c;因为整个操作都是在本地电脑完成的。 我们用 ollama 举例 首先安装 ollama https://ol…

数组-二分查找

二分查找 leetcode704 /*** param {number[]} nums* param {number} target* return {number}*/ var search function(nums, target) {let left 0, right nums.length - 1;while (left < right) {const mid Math.floor((right - left) / 2) left;const num nums[mid]…

SpringSecurity中文文档(体系结构).md

体系结构&#xff08;Architecture&#xff09; 本节讨论基于 Servlet 的应用程序中 Spring Security 的高级体系结构。我们将在参考文献的身份验证、授权和防止利用部分中构建这种高层次的理解。 过滤器的综述 &#xff08;A Review of Filters&#xff09; Spring Securit…

实现WebSocket聊天室功能

实现WebSocket聊天室功能 什么是WebSocket&#xff1f;WebSocket的工作原理服务器端实现客户端实现 在现代Web开发中&#xff0c;实时通信已经变得越来越重要。传统的HTTP协议由于其无状态和单向通信的特点&#xff0c;无法很好地满足实时通信的需求。而WebSocket协议则应运而生…

【讨论C++继承】

讨论C继承 继承定义继承方式和访问限定符 基类和派生类的赋值转换继承中的作用域派生类的默认成员函数继承和友元继承和静态成员菱形继承虚拟继承 继承是面向对象程序设计中&#xff0c;使代码可以复用的重要手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展。 继…

【ONLYOFFICE】| 桌面编辑器从0-1使用初体验

目录 一. &#x1f981; 写在前面二. &#x1f981; 在线使用感受2.1 创建 ONLYOFFICE 账号2.2 编辑pdf文档2.3 pdf直接创建表格 三. &#x1f981; 写在最后 一. &#x1f981; 写在前面 所谓桌面编辑器就是一种用于编辑文本、图像、视频等多种自媒体的软件工具&#xff0c;具…

算法训练营day24--93.复原IP地址 +78.子集 +90.子集II

一、93.复原IP地址 题目链接&#xff1a;https://leetcode.cn/problems/restore-ip-addresses/ 文章讲解&#xff1a;https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1fA4y1o715 1.1 初…

d3dcompiler_47.dll缺失怎么修复?d3dcompiler_47.dll修复使用说明

d3dcompiler_47.dll是一个重要的系统文件&#xff0c;属于MicrosoftWindows操作系统中Direct3D的一部分&#xff0c;它主要负责处理在Windows上运行的应用程序和游戏中的3D图形编程。这个DLL文件是“DirectX”的一项组成部分&#xff0c;DirectX是一套核心技术&#xff0c;用于…

13-Django项目--文件上传

目录 前端展示 路由: 数据库字段: 函数视图: 前端展示 {% extends "index/index.html" %}{% block content %}<div class"container"><input type"button" id"btnAdd" value"上传荣耀" class"btn btn-succ…

Oracle 集群的守护进程

ohas&#xff1a;主要用于守护cluster ware进程&#xff0c;在单节点建立集群的时候&#xff0c;没有crs&#xff0c;只有ohas、cluster ware GPnP&#xff1a;管理clusterware的配置信息&#xff0c;放在本地磁盘上 crs&#xff1a;管理clusterware中的资源&#xff0c;数据库…

成功解决ES高亮内容引起的字段显示不一致问题

在处理搜索引擎&#xff08;如Elasticsearch&#xff09;结果时&#xff0c;常见需求之一是对用户搜索的关键词进行高亮显示&#xff0c;这有助于用户快速识别搜索结果为何与其查询相关。但在实际应用中&#xff0c;如果处理不当&#xff0c;直接使用高亮片段可能会导致原始数据…

A股站不稳3000点让人稀罕不已啊

今天的A股&#xff0c;让人稀罕不已&#xff0c;你知道是为什么吗&#xff1f;盘面出现2个重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市冲了下3000点&#xff0c;第一个主题炒作的热点终于出现了&#xff0c;税改方向的行情发酵&#xff0c;并带动着其他改革相关方…

echarts的折线图实现部分虚线部分实线

场景&#xff1a; 折线图一般都是实线为准&#xff0c;但是由于最后一个数据是预测。所以想要实现最后一段为虚线。 效果图&#xff1a; 具体实现&#xff1a; series:[{name: "销售总金额",type: "line",smooth: true,barWidth: 10,stack: Total,itemSty…

Ubuntu下反弹shell的思考

目录 Ubuntu的命令执行环境 bash (Bourne Again SHell): sh (Bourne SHell): dash (Debian Almquist SHell): 它们之间的关系&#xff1a; 可能遇到的问题 一、脚本权限问题 二、命令执行环境(shell解释器)问题 如何解决&#xff1f; 1.修改/bin/sh软连接的指向为bas…

ESP32CAM物联网教学01

ESP32CAM物联网教学01 拍照 视频 这么小的一个开发板都带上摄像头了&#xff0c;能拍照&#xff1f;能视频吗&#xff1f;现在就跟着我做起来。 初识ESP32CAM 我们到淘宝搜索“ESP32Cam”&#xff0c;就能买到这样一块开发板。 ESP32Cam是双核处理器&#xff0c;提供WIFI和…

Cyuyanzhong的内存函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、memcpy函数的使用与模拟实现二、memmove函数的使用和模拟实现三、memset函数与memcmp函数的使用&#xff08;一&#xff09;、memset函数&#xff08;内存块…

Linux shell编程学习笔记59: ps 获取系统进程信息,类似于Windows系统中的tasklist 命令

0 前言 系统进程信息是电脑网络信息安全检查中的一块重要内容&#xff0c;对于使用Linux和基于Linux作为操作系统的电脑来说&#xff0c;可以使用ps命令。 1 ps命令 的功能、格式和选项说明 1.1 ps命令 的功能 Linux 中的ps&#xff08;意为&#xff1a;process status&…

Chrome导出cookie的实战教程

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

leetcode刷题:vector刷题

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;leetcode刷题 1.只出现一次的数字 这道题很简单&#xff0c;我们只需要遍历一次数组即可通过异或运算实现。(一个数与自身异或结果为0&#xff0c;任何数与0异或还是它本身) class Solut…