feat(Go-Tool):2020/12/15:添加前缀树工具,实现前缀搜索和字符串搜索功能

This commit is contained in:
Huangzj
2020-12-15 09:49:54 +08:00
parent fa31b23cca
commit 6fed950fa9
5 changed files with 307 additions and 0 deletions
+15
View File
@@ -8,6 +8,15 @@ package set
import "sync"
type ISet interface {
Add(i interface{})
AddAll(list ...interface{})
Remove(i interface{})
Contains(i interface{}) bool
IsEmpty() bool
GetAllSet() []interface{}
}
type Set struct {
setMap sync.Map //用来存储元素的map
}
@@ -16,6 +25,12 @@ func (set *Set) Add(i interface{}) {
set.setMap.Store(i, nil)
}
func (set *Set) AddAll(list ...interface{}) {
for _, item := range list {
set.Add(item)
}
}
func (set *Set) Remove(i interface{}) {
set.setMap.Delete(i)
}
+156
View File
@@ -0,0 +1,156 @@
/*
* @Author : huangzj
* @Time : 2020/12/14 16:15
* @Description:前缀树\字典树
*/
package trie
import "github.com/ahmetb/go-linq"
type Trie struct {
Child map[rune]*Trie //存储当前字符(Rune)下面对应的Rune的集合,使用Map可以提高查找效率
Word string //存储字典树上有的字符串信息,如果Word是空字符串(""),则代表没有在该节点找到对应的字符串信息
}
const (
emptyTag = ""
)
type WordFunc func(sentence []rune, start int, end int) string
func New() *Trie {
return initTrie()
}
//初始化Trie
func initTrie() *Trie {
return &Trie{
Child: make(map[rune]*Trie, 0),
Word: emptyTag,
}
}
//往前缀树中添加对应的字符串(build模式)
func (trie *Trie) insert(word string) *Trie {
traverse := trie
for _, v := range []rune(word) {
if _, ok := traverse.Child[v]; !ok {
newTrie := initTrie()
traverse.Child[v] = newTrie
}
traverse = traverse.Child[v]
}
traverse.Word = word
return trie
}
//删除对应的字符串,如果查找不到则直接返回(build模式)
func (trie *Trie) Delete(word string) *Trie {
traverse := trie
for _, v := range []rune(word) {
if _, ok := traverse.Child[v]; !ok {
return trie
}
traverse = traverse.Child[v]
}
traverse.Word = emptyTag
return trie
}
//查找对应的字符串,找到则返回true,没找到则返回false
func (trie *Trie) Search(word string) bool {
traverse := trie
for _, v := range []rune(word) {
if _, ok := traverse.Child[v]; !ok {
return false
}
traverse = traverse.Child[v]
}
if traverse.Word != emptyTag {
return true
}
return false
}
//遍历前缀树存储的所有字符串
func (trie *Trie) Traverse() []string {
result := make([]string, 0)
result = traverse(trie, result)
return result
}
func traverse(trie *Trie, result []string) []string {
for _, value := range trie.Child {
if value.Word != emptyTag {
result = append(result, value.Word)
}
//进行子的路径遍历
if len(value.Child) != 0 {
result = traverse(value, result)
}
}
return result
}
//查找字符串中匹配的子字符串,进行自定义处理
func (trie *Trie) DealFunc(sentence string, wordFunc WordFunc) string {
traverse := trie
for index, v := range []rune(sentence) {
//从句子的字符开始往下查找,找到最后一个对应的字符(如果uuck和uucku都是敏感词汇的话,会把uucku设置为*,而不只是uuck.)
if _, ok := traverse.Child[v]; ok {
traverse = traverse.Child[v]
continue
}
if traverse.Word != emptyTag {
//传入到wordFunc里面的是字符串的rune数组、查询到的词汇在字符串中的开始位置、查询到的词汇在字符串中的结束位置
sentence = wordFunc([]rune(sentence), index-len([]rune(traverse.Word)), index)
}
//trie从头开始
traverse = trie
}
return sentence
}
//搜索前缀匹配的字符串集合(精准匹配)
func (trie *Trie) SearchTrie(word string) ([]string, bool) {
traverse := trie
for _, value := range []rune(word) {
if _, ok := traverse.Child[value]; !ok {
return []string{}, false
}
traverse = traverse.Child[value]
}
return traverse.Traverse(), true
}
//搜索前缀匹配的字符串集合(模糊匹配)
func (trie *Trie) SearchTrieVague(word string) ([]string, bool) {
var tries []*Trie
traverse := trie
for _, value := range []rune(word) {
if _, ok := traverse.Child[value]; !ok {
break
}
traverse = traverse.Child[value]
tries = append(tries, traverse)
}
result := make([]string, 0)
for _, item := range tries {
result = append(result, item.Traverse()...)
}
if len(result) == 0 {
return []string{}, false
}
realResult := make([]string, 0)
linq.From(result).Distinct().Sort(func(i, j interface{}) bool {
if i.(string) > j.(string) {
return true
}
return false
}).ToSlice(&realResult) //去重
return realResult, true
}
+115
View File
@@ -0,0 +1,115 @@
/*
* @Author : huangzj
* @Time : 2020/12/14 16:15
* @Description
*/
package trie
import (
"fmt"
"testing"
)
func TestTrie(t *testing.T) {
commonTest() //常规功能测试
sensitiveWordsTest() //敏感词汇屏蔽测试
searchTrie() //查找前缀匹配的所有字符串
searchTrieVague() //模糊查询前缀匹配的所有字符串
}
func searchTrieVague() {
fmt.Println()
fmt.Println("模糊查询 查找前缀匹配的所有字符串")
trie := New()
trie.insert("abcuuid").insert("abcuued").insert("abcMxns").insert("abssde").insert("avddfa")
trie.insert("我爱中国").insert("我爱祖国").insert("我爱学习").insert("我是谁").insert("我在哪里")
trie.insert("我是你").insert("我").insert("我喜欢学习").insert("我在厦门").insert("我是一个程序员")
fmt.Println()
fmt.Println()
result3, _ := trie.SearchTrieVague("我爱她")
for _, r := range result3 {
fmt.Println(r)
}
}
func searchTrie() {
fmt.Println()
fmt.Println("查找前缀匹配的所有字符串")
trie := New()
trie.insert("abcuuid").insert("abcuued").insert("abcMxns").insert("abssde").insert("avddfa")
trie.insert("我爱中国").insert("我爱祖国").insert("我爱学习").insert("我是谁").insert("我在哪里")
fmt.Println()
fmt.Println()
result, _ := trie.SearchTrie("abc")
for _, r := range result {
fmt.Println(r)
}
fmt.Println()
fmt.Println()
result1, _ := trie.SearchTrie("我爱")
for _, r := range result1 {
fmt.Println(r)
}
fmt.Println()
fmt.Println()
result2, _ := trie.SearchTrie("我爱她")
for _, r := range result2 {
fmt.Println(r)
}
}
func sensitiveWordsTest() {
trie := New()
trie.insert("粗话").insert("fuck").insert("fuckk").insert("脏话")
s := "我是脏话,我是粗话,fuckk,fuck,fffuccck"
word := trie.DealFunc(s, func(sentence []rune, start int, end int) string {
var replace string
for i := 0; i < end-start; i++ {
replace = replace + "*"
}
runes := append(sentence[0:start], []rune(replace)...)
runes = append(runes, sentence[end:]...)
return string(runes)
})
fmt.Println(word)
}
func commonTest() {
trie := New()
trie.insert("ABC").insert("AB").insert("ABE").insert("ABEX").insert("XYZ").insert("你").insert("你好").insert("你是谁")
result := trie.Traverse()
for _, r := range result {
fmt.Println(r)
}
fmt.Println()
fmt.Println("进行查找测试")
fmt.Println(trie.Search("Abc"))
fmt.Println(trie.Search("ABC"))
fmt.Println(trie.Search("ABEX"))
fmt.Println(trie.Search("ABEx"))
fmt.Println(trie.Search("你好"))
fmt.Println(trie.Search("你好吗"))
fmt.Println("进行删除测试")
trie.Delete("你").Delete("ABC")
fmt.Println(trie.Search("你"))
fmt.Println(trie.Search("你好"))
fmt.Println(trie.Search("你是谁"))
fmt.Println(trie.Search("AB"))
fmt.Println(trie.Search("ABE"))
fmt.Println(trie.Search("ABC"))
fmt.Println()
fmt.Println("再次遍历所有的字典")
result = trie.Traverse()
for _, r := range result {
fmt.Println(r)
}
}
+19
View File
@@ -0,0 +1,19 @@
## 1、什么是前缀树
参考知乎的解答:[前缀树](https://www.zhihu.com/question/318375802/answer/663596639)
前缀树在字符串查找起到很重要的作用,它的key都为字符串,能做到高效查询和插入,时间复杂度为O(k),k为字符串长度,缺点是如果大量字符串没有共同前缀时很耗内存。
## 2、前缀树的作用
1. 前缀匹配
2. 字符串检索
## 3、提供方法
1、往前缀树中添加对应的字符串(build模式)
2、删除对应的字符串,如果查找不到则直接返回(build模式)
3、查找对应的字符串,找到则返回true,没找到则返回false
4、遍历前缀树存储的所有字符串
5、查找字符串中匹配的子字符串,进行自定义处理
6、搜索前缀匹配的字符串集合(精准匹配)
7、搜索前缀匹配的字符串集合(模糊匹配)