feat(Go-Tool):2020/12/15:添加前缀树工具,实现前缀搜索和字符串搜索功能
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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