feat(Go-Tool):2020/12/15:添加前缀树工具,实现前缀搜索和字符串搜索功能
This commit is contained in:
@@ -68,6 +68,8 @@
|
|||||||
|
|
||||||
2020/12/14:添加validator.v8源码解析和使用示例
|
2020/12/14:添加validator.v8源码解析和使用示例
|
||||||
|
|
||||||
|
2020/12/15:添加前缀树工具,实现前缀搜索和字符串搜索功能
|
||||||
|
|
||||||
# mod vendor模式加载包
|
# mod vendor模式加载包
|
||||||
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到
|
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ package set
|
|||||||
|
|
||||||
import "sync"
|
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 {
|
type Set struct {
|
||||||
setMap sync.Map //用来存储元素的map
|
setMap sync.Map //用来存储元素的map
|
||||||
}
|
}
|
||||||
@@ -16,6 +25,12 @@ func (set *Set) Add(i interface{}) {
|
|||||||
set.setMap.Store(i, nil)
|
set.setMap.Store(i, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (set *Set) AddAll(list ...interface{}) {
|
||||||
|
for _, item := range list {
|
||||||
|
set.Add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (set *Set) Remove(i interface{}) {
|
func (set *Set) Remove(i interface{}) {
|
||||||
set.setMap.Delete(i)
|
set.setMap.Delete(i)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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、搜索前缀匹配的字符串集合(模糊匹配)
|
||||||
Reference in New Issue
Block a user