【Go】-倒排索引的简单实现

目录

什么是倒排索引

定义

基本结构和原理

分词在倒排索引中的重要性

简单倒排索引的实现

接口定义

简单数据库的实现

倒排索引

正排索引

测试

总结


什么是倒排索引

定义

  • 倒排索引(Inverted Index)是一种索引数据结构,它是文档检索系统中最常用的数据结构之一。在信息检索领域,它用于快速地定位包含给定查询词的文档。与正向索引(Forward Index)相对,正向索引是从文档到词汇的映射,而倒排索引是从词汇到文档的映射。

基本结构和原理

  • 倒排索引主要由两部分组成:词汇表(Vocabulary)和倒排记录表(Postings List)。

  • 词汇表:包含了文档集合中出现的所有不同的词汇(或词条)。每个词汇都有一个指向其对应的倒排记录表的指针。例如,在一个包含多篇新闻文章的文档集合中,词汇表可能包含 “经济”“政治”“科技” 等词汇。

  • 倒排记录表:对于词汇表中的每个词汇,倒排记录表记录了包含该词汇的所有文档的标识符(Document ID)以及可能的其他相关信息,如词汇在文档中的位置、出现的频率等。比如,对于词汇 “科技”,其倒排记录表可能包含文档 ID 为 1、3、5 的记录,表示这三篇文档中都出现了 “科技” 这个词汇。

    • 假如现在有三份数据文档,内容分别是:

       Doc 1:Java is the best programming language​Doc 2:PHP is the best programming language​Doc 3:Javascript is the best programming language

      为了创建索引,通过分词器将每个文档的内容拆成单独的词,再将这些词条创建成不含重复词条的排序列表,然后列出每个词条出现在哪个文档,结果如下:

      termDoc 1Doc 2Doc 3
      Java
      is
      the
      best
      programming
      language
      PHP
      Javascript

              这种结构由文档中所有不重复的词的列表构成,对于其中每个词都有至少一个文档与与之关联。这种由属性值来确定记录的位置的结构就是倒排索引,带有倒排索引的文件被称为倒排文件。

              将上表转为更直观的图片来展示倒排索引:

      image-20241211221224035

分词在倒排索引中的重要性

  • 建立索引基础:倒排索引是一种用于快速检索的数据结构。它的核心是将文档中的关键词提取出来,建立关键词到文档的映射关系。分词就是这个提取关键词的过程,只有通过分词,才能将文本内容分解为一个个有意义的词汇单元,为建立倒排索引提供基础。例如,对于一篇文档 “我爱自然语言处理技术”,如果不分词,这个文档就会被当作一个整体,很难进行有效的关键词检索;而通过分词得到 “我”“爱”“自然语言处理”“技术” 这些词汇后,就可以分别建立它们与该文档的索引关系。

  • 提高检索效率和准确性:当用户进行查询时,倒排索引会根据用户输入的关键词来查找相关文档。精确的分词可以确保查询词和索引中的词汇准确匹配,提高检索的准确性。例如,在搜索引擎中,如果用户输入 “自然语言处理”,经过良好分词的倒排索引能够快速定位到包含这个词汇的文档,而不会因为没有正确分词而错过相关文档。同时,合理的分词还可以减少索引的大小,提高检索效率。如果将一些无意义的组合词也作为索引词,会增加索引的复杂度和存储量,而通过分词去除不必要的组合,只保留有意义的词汇,可以使索引更加紧凑,检索速度更快。

中文分词面临的挑战:

  • 词汇的复杂性

    • 词的歧义性:中文中存在大量的歧义现象。例如 “下雨天留客天留我不留”,不同的断句(分词)方式会产生不同的意思。可以是 “下雨天,留客天,留我不?留。” 也可以是 “下雨天留客,天留,我不留。” 这种歧义给中文分词带来了很大的困难。

    • 新词不断涌现:随着社会的发展和科技的进步,新的词汇不断出现,如 “区块链”“人工智能”“元宇宙” 等。对于分词系统来说,需要及时识别这些新词,才能保证分词的准确性和完整性。

  • 缺乏明显的分隔符:与英文等语言不同,中文句子中词与词之间没有明显的分隔符(如英文中的空格)。这使得计算机很难直观地判断一个词的起始和结束位置,需要通过复杂的算法和规则来进行分词。

  • 语言的灵活性和多样性:中文有丰富的表达方式,包括成语、俗语、古诗词等。这些特殊的语言形式也给分词带来了挑战。例如,成语 “胸有成竹” 如果被错误地分割为 “胸有”“成竹”,就会失去原有的语义,影响分词的质量。

常用的中文分词库

  • jieba 分词库

    • 特点:jieba 是一个非常流行的中文分词库,它具有多种分词模式,包括精确模式、全模式和搜索引擎模式。精确模式试图将句子最精确地切开,适合文本分析等场景;全模式把句子中所有的可以成词的词语都扫描出来,速度快但可能会产生冗余;搜索引擎模式在精确模式的基础上,对长词再次切分,提高搜索引擎召回率。例如,对于句子 “中华人民共和国”,精确模式会分为 “中华人民共和国”,全模式会分为 “中华”“华人”“人民”“共和”“共和国” 等,搜索引擎模式会在精确模式的基础上对 “中华人民共和国” 进一步切分,以适应搜索引擎的需求。

    • 应用场景:广泛应用于文本挖掘、信息检索、机器翻译等领域。例如,在文本挖掘中,可以使用 jieba 对文本进行分词,然后进行词频统计等分析。

  • THULAC(清华大学自然语言处理与社会人文计算实验室)

    • 特点:它是由清华大学开发的中文词法分析工具包,具有较高的分词准确性。它提供了词性标注等功能,不仅可以分词,还可以标注每个词的词性,如名词、动词、形容词等。例如,对于句子 “他高兴地跑了”,除了将句子分为 “他”“高兴”“地”“跑”“了”,还可以标注出 “他” 是代词,“高兴” 是形容词,“地” 是助词,“跑” 是动词,“了” 是语气词。

    • 应用场景:在自然语言处理研究和需要词性标注的应用场景中使用较多,如情感分析中,结合词性标注可以更好地分析句子的情感倾向。


简单倒排索引的实现

接口定义

        定义三个接口,分别是 数据库,正排索引,倒排索引;规定都要实现GetAdd功能

// DB接口定义了数据库的基本操作,用于获取和添加数据。type DB interface {Get(string) []stringAdd(string)}​// ForwardIndexer 用于根据给定的文档ID列表获取对应的原始字符串内容。type ForwardIndexer interface {Get([]int64) []stringAdd(int64, string)}​// InvertedIndexer 用于根据给定的字符串获取对应的文档ID列表以及添加倒排索引数据。type InvertedIndexer interface {Get(string) []int64Add(string, int64)}

简单数据库的实现

        定义简单数据库结构体,由id,正排索引和倒排索引组成。

   Get方法是先通过倒排索引由字符串找出id,然后在通过正排索引由id找出匹配的字符串

   Add方法把字符串存入倒排和正排

// SimpleDatabase结构体实现了DB接口,内部整合了正向索引和倒排索引来管理数据。type SimpleDatabase struct {id int64fi ForwardIndexerii InvertedIndexer}​// NewSimpleDatabase创建一个新的SimpleDatabase实例,初始化其正向索引和倒排索引相关组件。func NewSimpleDatabase() DB {return &SimpleDatabase{id: 0, // 递增文档IDfi: NewForwardIndex(),ii: NewSimpleInverted(),}}​func (sd *SimpleDatabase) Get(s string) []string {// 先通过倒排索引根据输入字符串获取对应的文档ID列表ids := sd.ii.Get(s)// 再通过正排索引根据获取到的文档ID列表查找对应的原字符串return sd.fi.Get(ids)}​func (sd *SimpleDatabase) Add(s string) {atomic.AddInt64(&sd.id, 1)id := sd.idaddToIndexes(sd.fi, sd.ii, id, s)}​func addToIndexes(fi ForwardIndexer, ii InvertedIndexer, id int64, s string) {// 倒排存入ii.Add(s, id)// 正排存入fi.Add(id, s)}

倒排索引

        倒排索引结构由读写锁,分词器和map组成

   Get方法查找data返回id数组

   Add先分词,然后把分词后的结构以及对应id存入map,ES中的分词器一般会大写转小写,但是这里我偷个懒就直接存了

// SimpleInverted结构体实现了InvertedIndexer接口,用于管理倒排索引数据。type SimpleInverted struct {sync.RWMutexdata     map[string][]int64analyzer Analyzer}​// NewSimpleInverted创建一个新的SimpleInverted实例,初始化倒排索引数据存储结构和分析器。func NewSimpleInverted() InvertedIndexer {return &SimpleInverted{data:     make(map[string][]int64),analyzer: NewSimpleAnalyzer(),}}​func (si *SimpleInverted) Get(s string) []int64 {si.RLock()result := si.data[s]si.RUnlock()return result}​func (si *SimpleInverted) Add(s string, id int64) {words := si.analyzer.Analyze(s)si.Lock()for _, word := range words {si.data[word] = append(si.data[word], id)}si.Unlock()}

分词器

        这里分词器的实现比较简单,直接逐个拆开来存了,在实际中分词器比这更加复杂和优雅,往往伴随着一些分词的算法

        这里用使用了两层嵌套的 for 循环来生成输入字符串的所有可能子串,并将这些子串作为键存入一个 map 类型的变量 word 中。外层循环控制起始位置 i,内层循环控制结束位置 j,通过切片操作 su[i:j] 取出从位置 i 到位置 j(不包含 j)的子串,然后将其转换为字符串作为 map 的键,对应的值使用了空结构体 struct{}{}

        这样做虽然能实现分词,但是非常暴力而且浪费空间。因为中文分词不像英文,可以使用空格或者,进行简单切分,一般的中文分词器都会采用词典分词,因为我们是简单实现,所以这里就采用了这种暴力写法(其实是太菜了不会更好的分词方法)

 // Analyzer接口定义了文本分析(例如分词等操作)的基本操作方法。type Analyzer interface {Analyze(s string) []string}​// SimpleAnalyzer结构体实现了Analyzer接口,简单地进行字符串分析(示例中较简单的逻辑,可优化)。type SimpleAnalyzer struct{}​// NewSimpleAnalyzer创建一个新的SimpleAnalyzer实例。func NewSimpleAnalyzer() Analyzer {return &SimpleAnalyzer{}}​func (l *SimpleAnalyzer) Analyze(s string) (re []string) {// 转为rune可以有效处理中英文字符的字节大小问题su := []rune(s)sl := len(su)word := make(map[string]struct{})for i := 0; i < sl; i++ {for j := i + 1; j <= sl; j++ {word[string(su[i:j])] = struct{}{}}}re = make([]string, len(word))num := 0for index := range word {re[num] = indexnum++}return}

正排索引

        这个比起倒排简单很多,没啥好讲的

// forwardIndex结构体实现了ForwardIndexer接口,用于管理正向索引数据。type forwardIndex struct {sync.RWMutexdata map[int64]string}​// NewForwardIndex创建一个新的forwardIndex实例,初始化正向索引数据存储结构。func NewForwardIndex() ForwardIndexer {return &forwardIndex{data: make(map[int64]string),}}​func (fi *forwardIndex) Get(ids []int64) (re []string) {re = make([]string, len(ids))fi.RLock()for k, v := range ids {re[k] = fi.data[v]}fi.RUnlock()return}​func (fi *forwardIndex) Add(id int64, s string) {fi.Lock()fi.data[id] = sfi.Unlock()}

测试

        单元测试代码如下,一首《春江花月夜》来试试效果

func TestSimpleDatabaseWithChunJiangHuaYueYe(t *testing.T) {// 创建数据库实例db := NewSimpleDatabase()​// 添加《春江花月夜》的诗句(假设逐句添加)lines := []string{"春江潮水连海平,海上明月共潮生。","江流宛转绕芳甸,月照花林皆似霰。","空里流霜不觉飞,汀上白沙看不见。","江天一色无纤尘,皎皎空中孤月轮。","江畔何人初见月?江月何年初照人?","人生代代无穷已,江月年年望相似。","不知江月待何人,但见长江送流水。","白云一片去悠悠,青枫浦上不胜愁。","谁家今夜扁舟子?何处相思明月楼?","可怜楼上月徘徊,应照离人妆镜台。","玉户帘中卷不去,捣衣砧上拂还来。","此时相望不相闻,愿逐月华流照君。","鸿雁长飞光不度,鱼龙潜跃水成文。","昨夜闲潭梦落花,可怜春半不还家。","江水流春去欲尽,江潭落月复西斜。","斜月沉沉藏海雾,碣石潇湘无限路。","不知乘月几人归,落月摇情满江树。",}for _, line := range lines {db.Add(line)}​// 测试获取包含“江”字的字符串expectedJiang := []string{"春江潮水连海平,海上明月共潮生。","江流宛转绕芳甸,月照花林皆似霰。","江天一色无纤尘,皎皎空中孤月轮。","江畔何人初见月?江月何年初照人?","人生代代无穷已,江月年年望相似。","不知江月待何人,但见长江送流水。","江水流春去欲尽,江潭落月复西斜。","不知乘月几人归,落月摇情满江树。",}actualJiang := db.Get("江")if !reflect.DeepEqual(actualJiang, expectedJiang) {t.Errorf("Get for '江' failed. Expected: %v, Got: %v", expectedJiang, actualJiang)} else {fmt.Println("========江=========")for _, s := range actualJiang {fmt.Println(s)}}​// 测试获取包含“月”字的字符串expectedYue := []string{"春江潮水连海平,海上明月共潮生。","江流宛转绕芳甸,月照花林皆似霰。","江天一色无纤尘,皎皎空中孤月轮。","江畔何人初见月?江月何年初照人?","人生代代无穷已,江月年年望相似。","不知江月待何人,但见长江送流水。","谁家今夜扁舟子?何处相思明月楼?","可怜楼上月徘徊,应照离人妆镜台。","此时相望不相闻,愿逐月华流照君。","江水流春去欲尽,江潭落月复西斜。","斜月沉沉藏海雾,碣石潇湘无限路。","不知乘月几人归,落月摇情满江树。",}actualYue := db.Get("月")if !reflect.DeepEqual(actualYue, expectedYue) {t.Errorf("Get for '月' failed. Expected: %v, Got: %v", expectedYue, actualYue)} else {fmt.Println("========月=========")for _, s := range actualYue {fmt.Println(s)}}​// 测试获取包含“花”字的字符串expectedHua := []string{"江流宛转绕芳甸,月照花林皆似霰。","昨夜闲潭梦落花,可怜春半不还家。",}actualHua := db.Get("花")if !reflect.DeepEqual(actualHua, expectedHua) {t.Errorf("Get for '花' failed. Expected: %v, Got: %v", expectedHua, actualHua)} else {fmt.Println("========花=========")for _, s := range actualHua {fmt.Println(s)}}​// 测试获取包含“海”字的字符串expectedHai := []string{"春江潮水连海平,海上明月共潮生。","斜月沉沉藏海雾,碣石潇湘无限路。",}actualHai := db.Get("海")if !reflect.DeepEqual(actualHai, expectedHai) {t.Errorf("Get for '海' failed. Expected: %v, Got: %v", expectedHai, actualHai)} else {fmt.Println("========海=========")for _, s := range actualHai {fmt.Println(s)}}}

        测试结果通过,可喜可贺

image-20241212205028259


总结

        Go简单实现了一下倒排索引,感觉分词还是很重要的,直接决定了整个倒排索引的表现,还是要多学习一些厉害的分词器是怎么实现的~

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

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

相关文章

智汇云舟4个案例入选“中国联通智慧城市物联感知与AI应用案例”

12月10日&#xff0c;由中国联通智慧城市军团联合联通数字科技有限公司物联网事业部、物联中国团体组织联席会共同主办的“中国联通首届智慧城市领域物联感知与AI应用优秀案例发布交流大会”在郑州举行。大会现场对50余个优秀案例进行了集中发布与表彰。智汇云舟凭借深厚的技术…

http 502 和 504 的区别

首先看一下概念&#xff1a; 502&#xff1a;作为网关或者代理工作的服务器尝试执行请求时&#xff0c;从上游服务器接收到无效的响应。503&#xff1a;由于临时的服务器维护或者过载&#xff0c;服务器当前无法处理请求。这个状况是临时的&#xff0c;并且将在一段时间以后恢…

博弈论3:图游戏SG函数(Graph Games)

目录 一、图游戏是什么 1.游戏特征 2.游戏实例 二、图游戏的必胜策略 1.SG 函数&#xff08;Sprague-Grundy Function&#xff09; 2.必胜策略&#xff08;利用SG函数&#xff09; 3.拿走游戏转化成图游戏&#xff08;Take-away Game -> Graph Game&#xff09; 一、图…

免费生成AI PPT产品推荐?

要完全免费几乎是没有的&#xff0c;要知道AI还是非常烧钱的。 不过免费蹭还是有很多方法的&#xff0c;这里收集了一些&#xff1a; 下面分享我自己免费蹭过的几款AI制作PPT的工具。 1 金山-WPS PPT对我们来说并不陌生&#xff0c;而微软的PowerPoint与金山的WPS也是我们最常…

Python机器视觉的学习

一、二值化 1.1 二值化图 二值化图&#xff1a;就是将图像中的像素改成只有两种值&#xff0c;其操作的图像必须是灰度图。 1.2 阈值法 阈值法&#xff08;Thresholding&#xff09;是一种图像分割技术&#xff0c;旨在根据像素的灰度值或颜色值将图像分成不同的区域。该方法…

Cisco Packet Tarcer配置计网实验笔记

文章目录 概要整体架构流程网络设备互连基础拓扑图拓扑说明配置步骤 RIP/OSPF混合路由拓扑图拓扑说明配置步骤 BGP协议拓扑图拓扑说明配置步骤 ACL访问控制拓扑图拓扑说明配置步骤 HSRP冗余网关拓扑图拓扑说明配置步骤 小结 概要 一些环境配置笔记 整体架构流程 网络设备互连…

【优选算法】二分算法(在排序数组中查找元素的第一个和最后一个位置,寻找峰值,寻找排序数组中的最小值)

二分算法简介&#xff1a; 提到二分我们可能都会想起二分查找&#xff0c;二分查找要求待查找的数组是有序的&#xff0c;与我们今天讲的二分算法不同&#xff0c;并不是数组元素严格按照有序排列才可以使用二分算法&#xff0c;只要数组中有一个点可以将数组分为两个部分&…

下载与使用PCL启动器(2.8.12正式版)

一.下载PCL启动器 PCL启动器下载官网&#xff1a;爱发电 连接创作者与粉丝的会员制平台将创作的自由还给创作者&#xff01;爱发电是让创作者简单地获得稳定收入的粉丝赞助平台。无论你在创作什么&#xff0c;都能在这里获得持续的资金支持&#xff0c;让创作从此更自由。htt…

【ArcGIS】基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作

第一章、以问题导入的方式&#xff0c;深入掌握原理基础【理论篇】 1、R语言入门&#xff1a; &#xff08;1&#xff09;安装R及集成开发环境&#xff08;IDE&#xff09;&#xff1b;&#xff08;2&#xff09;R语言基础语法与数据结构&#xff0c;包括&#xff1a;程序包安…

泊松编辑 possion editing图像合成笔记

开源地址&#xff1a; GitHub - kono-dada/Reproduction-of-possion-image-editing 掩码必须是矩形框

江科大笔记—DMA数据转运DMA+AD多通道

1. DMA初始化结构体详解 标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx为外设名称)&#xff0c;结构体成员用于设置外设工作参数&#xff0c; 并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器&#xff0c;达到配置外设工作环境的目的。…

程序算术题-2

程序算术题-2 输出所有组合逻辑实例代码 输出所有排列逻辑实例代码 输出所有组合 计算一组数字按n位数组合的所有组合。 逻辑 /*** param stringBuilder 用于组合的拼接* param list 组合数序列* param level 目前位数* param exceptedLevel 组合期待位数*/…

MAC M3电脑在idea上搭建Spark环境并跑通第一个程序

我的电脑是Macbook Pro&#xff0c;最近在学习Spark&#xff0c;想要在idea里搭建Spark环境&#xff0c;为之后的Spark编程作准备。下面是在MAC版本的idea里配置Spark环境。 1. 准备工作 1.安装 JDK 确保Mac 上已经安装了 JDK 8 或更高版本。 可通过 java -version 查看是否…

欧科云链研究院:AI时代,如何证明“我是我”?

OKG Research&#xff5c;编辑 近日&#xff0c;OpenAI 发布了新模型 Sora。这是一款高性能的文本到多模态生成工具&#xff0c;支持从文本生成精细的图像和动态视频。 相较早先发布的视频样例&#xff0c;该功能目前已经可以由用户真实上手体验&#xff0c;目前由于服务过载…

任务5 Web服务配置与管理

Web服务概述 Web服务简介 当今人们获取和传播信息的主要方式之一。 Web服务提供的资源多种多样&#xff0c;可能是简单的文本&#xff0c;也可能是图片、音频和视频等多媒体数据。 常用的浏览器有Chrome、Internet Explorer&#xff0c;以及Firefox等。 手机等移动设备成为…

Opencv之图像添加水印

一、实验原理 在图片处理领域&#xff0c;添加水印是一种常见的操作。通过叠加图像的方式&#xff0c;可以将水印无缝嵌入目标图像的指定位置。其基本原理包括以下步骤&#xff1a; 1、模板输入&#xff08;掩膜生成&#xff09;&#xff1a; 将水印图片转换为灰度图&#xf…

「Mac玩转仓颉内测版50」小学奥数篇13 - 动态规划入门

本篇将通过 Python 和 Cangjie 双语介绍动态规划的基本概念&#xff0c;并解决一个经典问题&#xff1a;斐波那契数列。学生将学习如何使用动态规划优化递归计算&#xff0c;并掌握编程中的重要算法思想。 关键词 小学奥数Python Cangjie动态规划斐波那契数列 一、题目描述 …

远程调试软件对比与使用推荐

远程调试软件对比与使用推荐 远程调试是现代软件开发中不可或缺的一部分&#xff0c;尤其是在处理分布式系统、云端服务或远程服务器上的问题时。以下是对几种常见远程调试工具的详细对比和推荐使用场景。 1. GDB (GNU Debugger) 特点 开源&#xff1a;完全免费且开源&…

HTML和JavaScript实现商品购物系统

下面是一个更全面的商品购物系统示例&#xff0c;包含新增商品、商品的增加删除以及结算找零的功能。这个系统使用HTML和JavaScript实现。 1.功能说明&#xff1a; 这个应用程序使用纯HTML和JavaScript实现。 包含一个商品列表和一个购物车区域。商品列表中有几个示例商品&a…

将带注释的Word文档改造成点击注释引用即可弹窗显示注释的HTML文档

阅读中国古籍电子书的时候&#xff0c;往往有很多注释。在正文和注释之间来回滚动页面是一件挺麻烦的事&#xff0c;比较方便的做法是将它编辑成通过点击注释引用即将注释弹出显示在引用所在位置的HTML文档&#xff0c;然后利用HTML文档制作成PDF或者epub文件&#xff0c;就比较…