golang高效前缀树(Prefix Tree)实现插件库ptrie的使用

Golang高效前缀树(Prefix Tree)实现插件库ptrie的使用

Trie (前缀树)

前缀树是一种空间优化的树形数据结构,其中每个节点都与其父节点合并。与常规树不同,前缀树中每个节点的键是按块进行比较的。

前缀树有以下应用场景:

  • 文本文档搜索
  • 基于规则的匹配
  • 为字符串键构建关联数组

字符比较复杂度:

  • 暴力搜索O(d n k)
  • 前缀树O(d log(k))

其中:

  • d:文档中的字符数
  • n:关键词数量
  • k:平均关键词长度

使用示例

1. 基本操作

package main

import (
	"fmt"
	"log"
	"github.com/viant/ptrie"
)

func main() {
	// 创建新的前缀树
	trie := ptrie.New()
	
	// 添加键值对
	pairs := map[string]interface{}{
		"apple":  1,
		"app":    2,
		"banana": 3,
	}
	
	for key, value := range pairs {
		if err := trie.Put([]byte(key), value); err != nil {
			log.Fatal(err)
		}
	}
	
	// 检查键是否存在
	has := trie.Has([]byte("apple"))
	fmt.Printf("Has 'apple': %v\n", has) // true
	
	// 获取键对应的值
	value, has := trie.Get([]byte("app"))
	fmt.Printf("Get 'app': %v, exists: %v\n", value, has) // 2, true
	
	// 匹配所有前缀
	input := []byte("application")
	matched := trie.MatchAll(input, func(key []byte, value interface{}) bool {
		fmt.Printf("matched: key: %s, value %v\n", key, value)
		return true
	})
	fmt.Printf("Total matches: %d\n", matched)
}

2. 构建和序列化

package main

import (
	"bytes"
	"log"
	"github.com/viant/ptrie"
)

func main() {
	// 创建并填充前缀树
	trie := ptrie.New()
	pairs := map[string]interface{}{
		"go":     "language",
		"golang": "awesome",
		"python": "scripting",
	}
	
	for key, value := range pairs {
		if err := trie.Put([]byte(key), value); err != nil {
			log.Fatal(err)
		}
	}
	
	// 序列化前缀树
	writer := new(bytes.Buffer)
	if err := trie.Encode(writer); err != nil {
		log.Fatal(err)
	}
	encoded := writer.Bytes()
	
	// encoded 可以保存到文件或云存储
}

3. 加载和反序列化

package main

import (
	"bytes"
	"log"
	"reflect"
	"github.com/viant/ptrie"
)

func main() {
	// 假设我们有一个序列化的前缀树
	var encoded []byte // 从文件或存储加载
	
	// 创建新的前缀树并指定值类型
	trie := ptrie.New()
	var v *string // 指定值的类型
	trie.UseType(reflect.TypeOf(v))
	
	// 反序列化
	reader := bytes.NewReader(encoded)
	if err := trie.Decode(reader); err != nil {
		log.Fatal(err)
	}
	
	// 现在可以使用反序列化的前缀树
	value, has := trie.Get([]byte("golang"))
	log.Printf("Get 'golang': %v, exists: %v", value, has)
}

4. 遍历前缀树

package main

import (
	"fmt"
	"github.com/viant/ptrie"
)

func main() {
	trie := ptrie.New()
	pairs := map[string]interface{}{
		"cat":  1,
		"car":  2,
		"cart": 3,
	}
	
	for key, value := range pairs {
		_ = trie.Put([]byte(key), value)
	}
	
	// 遍历所有键值对
	trie.Walk(func(key []byte, value interface{}) bool {
		fmt.Printf("key: %s, value %v\n", key, value)
		return true // 返回true继续遍历,false停止
	})
}

5. 前缀匹配

package main

import (
	"fmt"
	"github.com/viant/ptrie"
)

func main() {
	trie := ptrie.New()
	pairs := map[string]interface{}{
		"app":      1,
		"apple":    2,
		"applet":   3,
		"banana":   4,
		"band":     5,
	}
	
	for key, value := range pairs {
		_ = trie.Put([]byte(key), value)
	}
	
	input := []byte("apple pie")
	
	// 匹配输入的所有前缀
	matched := trie.MatchPrefix(input, func(key []byte, value interface{}) bool {
		fmt.Printf("Prefix matched: key: %s, value %v\n", key, value)
		return true
	})
	fmt.Printf("Total prefix matches: %d\n", matched)
}

性能基准测试

基准测试统计了以下文本中的所有单词:

Lorem Ipsum

  1. 短文本:平均行大小20,单词13
  2. 长文本:平均行大小711,单词551
Benchmark_LoremBruteForceShort-8    	  500000	      3646 ns/op
Benchmark_LoremTrieShort-8          	  500000	      2376 ns/op
Benchmark_LoremBruteForceLong-8     	    1000	   1612877 ns/op
Benchmark_LoremTrieLong-8           	   10000	    119990 ns/op

Hamlet

  1. 短文本:平均行大小20,单词49
  2. 长文本:平均行大小41,单词105
Benchmark_HamletBruteForceShort-8   	   30000	     44306 ns/op
Benchmark_HamletTrieShort-8         	  100000	     18530 ns/op
Benchmark_HamletBruteForceLong-8    	   10000	    226836 ns/op
Benchmark_HamletTrieLong-8          	   50000	     39329 ns/op

许可证

源代码根据Apache License, Version 2的条款提供,如LICENSE文件中所述。

作者

库作者: Adrian Witas


更多关于golang高效前缀树(Prefix Tree)实现插件库ptrie的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高效前缀树(Prefix Tree)实现插件库ptrie的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang高效前缀树(Prefix Tree)实现:ptrie库使用指南

前缀树(Trie)是一种高效存储和检索字符串集合的数据结构,特别适合实现自动完成、拼写检查和IP路由等场景。在Go语言中,ptrie是一个高效的前缀树实现库。

ptrie库安装

首先安装ptrie库:

go get github.com/viant/ptrie

基本使用示例

1. 创建前缀树并插入数据

package main

import (
	"fmt"
	"github.com/viant/ptrie"
)

func main() {
	trie := ptrie.New()
	
	// 插入键值对
	trie.Put([]byte("apple"), 1)
	trie.Put([]byte("app"), 2)
	trie.Put([]byte("application"), 3)
	trie.Put([]byte("banana"), 4)
	trie.Put([]byte("band"), 5)
	
	// 查找数据
	if value, ok := trie.Get([]byte("app")); ok {
		fmt.Printf("Found 'app': %v\n", value) // 输出: Found 'app': 2
	}
	
	// 检查是否存在
	if trie.Has([]byte("apple")) {
		fmt.Println("'apple' exists in trie")
	}
}

2. 前缀搜索

func prefixSearchExample() {
	trie := ptrie.New()
	trie.Put([]byte("apple"), 1)
	trie.Put([]byte("app"), 2)
	trie.Put([]byte("application"), 3)
	trie.Put([]byte("banana"), 4)
	trie.Put([]byte("band"), 5)
	
	// 查找所有以"app"为前缀的键
	var matches []interface{}
	trie.MatchPrefix([]byte("app"), func(key []byte, value interface{}) bool {
		matches = append(matches, value)
		return true // 继续搜索
	})
	
	fmt.Printf("Prefix 'app' matches: %v\n", matches) 
	// 输出: Prefix 'app' matches: [2 1 3]
}

3. 删除操作

func deleteExample() {
	trie := ptrie.New()
	trie.Put([]byte("apple"), 1)
	trie.Put([]byte("app"), 2)
	
	// 删除键
	if deleted := trie.Delete([]byte("app")); deleted {
		fmt.Println("'app' deleted successfully")
	}
	
	// 验证删除
	if !trie.Has([]byte("app")) {
		fmt.Println("'app' no longer exists")
	}
}

高级功能

1. 并发安全使用

ptrie本身不是并发安全的,但可以通过sync.Mutex实现并发安全:

import "sync"

type SafeTrie struct {
	trie *ptrie.Trie
	mu   sync.RWMutex
}

func (st *SafeTrie) Put(key []byte, value interface{}) {
	st.mu.Lock()
	defer st.mu.Unlock()
	st.trie.Put(key, value)
}

func (st *SafeTrie) Get(key []byte) (interface{}, bool) {
	st.mu.RLock()
	defer st.mu.RUnlock()
	return st.trie.Get(key)
}

2. 自定义序列化

func serializationExample() {
	trie := ptrie.New()
	trie.Put([]byte("key1"), "value1")
	trie.Put([]byte("key2"), "value2")
	
	// 序列化
	data, err := trie.MarshalBinary()
	if err != nil {
		panic(err)
	}
	
	// 反序列化
	newTrie := ptrie.New()
	if err := newTrie.UnmarshalBinary(data); err != nil {
		panic(err)
	}
	
	if value, ok := newTrie.Get([]byte("key1")); ok {
		fmt.Println("Deserialized value:", value)
	}
}

性能优化建议

  1. 批量插入:如果需要插入大量数据,考虑先排序再插入,可以提高内存局部性

  2. 内存优化:对于长字符串,考虑使用指针或引用而不是直接存储

  3. 适当预分配:如果知道大概的数据量,可以预分配空间

  4. 使用字节池:频繁创建[]byte会影响性能,可以使用sync.Pool来重用[]byte

var bytePool = sync.Pool{
	New: func() interface{} {
		return make([]byte, 0, 128)
	},
}

func getBytes() []byte {
	return bytePool.Get().([]byte)
}

func putBytes(b []byte) {
	b = b[:0]
	bytePool.Put(b)
}

ptrie库提供了高效的前缀树实现,特别适合需要快速前缀匹配的场景。通过合理使用,可以显著提高字符串检索和匹配的性能。

回到顶部