上海的油菜花开了,所以咱们来手写个倒排索引吧。
需求
准备学习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"))
输出结果,祝大家长风破浪。
========,=========
金樽清酒斗十千,玉盘珍羞直万钱。
停杯投箸不能食,拔剑四顾心茫然。
欲渡黄河冰塞川,将登太行雪满山。
闲来垂钓碧溪上,忽复乘舟梦日边。
行路难,行路难,多歧路,今安在?
长风破浪会有时,直挂云帆济沧海。
========难=========
行路难,行路难,多歧路,今安在?
========。=========
金樽清酒斗十千,玉盘珍羞直万钱。
停杯投箸不能食,拔剑四顾心茫然。
欲渡黄河冰塞川,将登太行雪满山。
闲来垂钓碧溪上,忽复乘舟梦日边。
长风破浪会有时,直挂云帆济沧海。
========长风破浪=========
长风破浪会有时,直挂云帆济沧海。
努力没结果,那就更努力一些~
算法梦想家,来跟我一起玩算法,玩音乐,聊聊文学创作,咱们一起天马行空!
