手写倒排索引 第一场第一镜

1,006 阅读3分钟

上海的油菜花开了,所以咱们来手写个倒排索引吧。

需求

准备学习es,老规矩,先学原理然后直接手写。倒排索引先分词,然后每个词映射到id的列表,正向索引就是id映射到原字符串。先实现大概意思就好,后边经过学习之后逐步完善。

接口

定个简单的数据库、倒排索引、正向索引的接口,方法名就先凑合起,暂时有搜素跟新增就够。

type DB interface {
    //参数是短词
	Get(string) []string
	//参数是原串
	Add(string)
}
type ForwardIndex interface {
	Get([]int64) []string
	Add(int64, string)
}
type InvertedIndex interface {
	Get(string) []int64
	Add(string, int64)
}

数据库实现

简单实现个数据库,新增的时候,id原子增加就好,获取的时候用短词去倒排索引上找id列表,然后拿着列表去正向索引获取原串列表。

type sillyDatabase struct {
	id int64
	fi forward_index.ForwardIndex
	ii inverted_index.InvertedIndex
}

func NewSillyDatabase() DB {
	return &sillyDatabase{
		id: 0,
		fi: forward_index.NewForwardIndex(),
		ii: inverted_index.NewSillyInverted(),
	}
}

func (sd *sillyDatabase) Get(s string) []string {
	//倒排取ids
	//正牌取原串
	return sd.fi.Get(sd.ii.Get(s))
}

func (sd *sillyDatabase) Add(s string) {
	atomic.AddInt64(&sd.id, 1)
	//倒排
	sd.ii.Add(s, sd.id)
	//正排
	sd.fi.Add(sd.id, s)
}

倒排索引实现

没啥说的,新增的时候先分词就好。

type sillyInverted struct {
	sync.RWMutex
	data  map[string][]int64
	lexer lexer.Lexer
}

func NewSillyInverted() InvertedIndex {
	return &sillyInverted{
		data:  map[string][]int64{},
		lexer: lexer.NewSillyLexer(),
	}
}

func (si *sillyInverted) Get(s string) (re []int64) {
	si.RLock()
	defer si.RUnlock()
	if v, ok := si.data[s]; ok {
		re = v
	}
	return
}

func (si *sillyInverted) Add(s string, id int64) {
	words := si.lexer.Lex(s)
	si.Lock()
	defer si.Unlock()
	for _, v := range words {
		si.data[v] = append(si.data[v], id)
	}
}


分词实现

分词先实现一个暴力的,时间复杂度O(n^2),把字符串所有的情况都枚举出来,可想而知这是极端费空间的,后期要好好学习一下聪明一些的分词手法。

func NewSillyLexer() Lexer {
	return &sillyLexer{}
}

func (l *sillyLexer) Lex(s string) []string {
	return l.force(s)
}

func (l *sillyLexer) force(s string) (re []string) {
	su := []rune(s)
	sl := len(su)
	have := map[string]struct{}{}
	for i := 0; i < sl; i++ {
		for j := i + 1; j <= sl; j++ {
			have[string(su[i:j])] = struct{}{}
		}
	}
	re = make([]string, len(have))
	num := 0
	for k, _ := range have {
		re[num] = k
		num++
	}
	return
}

正向索引实现

这个就更简单了,一个map足够满足现在的我。

type forwardIndex struct {
	sync.RWMutex
	data map[int64]string
}

func NewForwardIndex() ForwardIndex {
	return &forwardIndex{
		data: map[int64]string{},
	}
}

func (fi *forwardIndex) Get(ids []int64) (re []string) {
	re = make([]string, len(ids))
	fi.RLock()
	defer fi.RUnlock()
	for k, v := range ids {
		re[k] = fi.data[v]
	}
	return
}

func (fi *forwardIndex) Add(id int64, s string) {
	fi.Lock()
	defer fi.Unlock()
	fi.data[id] = s
}

结果

中二病犯了,用一首励志的诗来试试,可以先想想结果是啥哈哈。

db := database.NewSillyDatabase()
db.Add("金樽清酒斗十千,玉盘珍羞直万钱。")
db.Add("停杯投箸不能食,拔剑四顾心茫然。")
db.Add("欲渡黄河冰塞川,将登太行雪满山。")
db.Add("闲来垂钓碧溪上,忽复乘舟梦日边。")
db.Add("行路难,行路难,多歧路,今安在?")
db.Add("长风破浪会有时,直挂云帆济沧海。")
fmt.Println("========,=========")
fmt.Println(strings.Join(db.Get(","), "\n"))
fmt.Println("========难=========")
fmt.Println(strings.Join(db.Get("难"), "\n"))
fmt.Println("========。=========")
fmt.Println(strings.Join(db.Get("。"), "\n"))
fmt.Println("========长风破浪=========")
fmt.Println(strings.Join(db.Get("长风破浪"), "\n"))

输出结果,祝大家长风破浪。

========,=========
金樽清酒斗十千,玉盘珍羞直万钱。
停杯投箸不能食,拔剑四顾心茫然。
欲渡黄河冰塞川,将登太行雪满山。
闲来垂钓碧溪上,忽复乘舟梦日边。
行路难,行路难,多歧路,今安在?
长风破浪会有时,直挂云帆济沧海。
========难=========
行路难,行路难,多歧路,今安在?
========。=========
金樽清酒斗十千,玉盘珍羞直万钱。
停杯投箸不能食,拔剑四顾心茫然。
欲渡黄河冰塞川,将登太行雪满山。
闲来垂钓碧溪上,忽复乘舟梦日边。
长风破浪会有时,直挂云帆济沧海。
========长风破浪=========
长风破浪会有时,直挂云帆济沧海。

努力没结果,那就更努力一些~

算法梦想家,来跟我一起玩算法,玩音乐,聊聊文学创作,咱们一起天马行空!

在这里插入图片描述