feat: utils 下新增 sorts.Topological 拓扑排序函数
This commit is contained in:
parent
82973dd11a
commit
7a5e2c1e7e
|
@ -0,0 +1,7 @@
|
|||
package sorts
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrCircularDependencyDetected = errors.New("circular dependency detected")
|
||||
)
|
|
@ -0,0 +1,60 @@
|
|||
package sorts
|
||||
|
||||
type topologicalNode[V any] struct {
|
||||
value V
|
||||
dependsOn []*topologicalNode[V]
|
||||
dependents []*topologicalNode[V]
|
||||
}
|
||||
|
||||
// Topological 拓扑排序是一种对有向图进行排序的算法,它可以用来解决一些依赖关系的问题,比如计算字段的依赖关系。拓扑排序会将存在依赖关系的元素进行排序,使得依赖关系的元素总是排在被依赖的元素之前。
|
||||
// - slice: 需要排序的切片
|
||||
// - queryIndexHandler: 用于查询切片中每个元素的索引
|
||||
// - queryDependsHandler: 用于查询切片中每个元素的依赖关系,返回的是一个索引切片,如果没有依赖关系,那么返回空切片
|
||||
//
|
||||
// 该函数在存在循环依赖的情况下将会返回 ErrCircularDependencyDetected 错误
|
||||
func Topological[Index comparable, V any](slice []V, queryIndexHandler func(item V) Index, queryDependsHandler func(item V) []Index) ([]V, error) {
|
||||
|
||||
// 1. 创建一个依赖关系图
|
||||
var nodes = make(map[Index]*topologicalNode[V])
|
||||
|
||||
for _, item := range slice {
|
||||
node := &topologicalNode[V]{value: item}
|
||||
nodes[queryIndexHandler(item)] = node
|
||||
}
|
||||
|
||||
for _, item := range slice {
|
||||
depends := queryDependsHandler(item)
|
||||
for _, depend := range depends {
|
||||
if node, exists := nodes[depend]; exists {
|
||||
node.dependsOn = append(node.dependsOn, nodes[queryIndexHandler(item)])
|
||||
node.dependents = append(node.dependents, nodes[queryIndexHandler(item)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sorted = make([]V, 0, len(slice))
|
||||
var visited = make(map[Index]bool)
|
||||
|
||||
var visit func(node *topologicalNode[V])
|
||||
visit = func(node *topologicalNode[V]) {
|
||||
index := queryIndexHandler(node.value)
|
||||
if node == nil || visited[index] {
|
||||
return
|
||||
}
|
||||
visited[index] = true
|
||||
for _, n := range node.dependsOn {
|
||||
visit(n)
|
||||
}
|
||||
sorted = append(sorted, node.value)
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
visit(node)
|
||||
}
|
||||
|
||||
if len(sorted) != len(slice) {
|
||||
return nil, ErrCircularDependencyDetected
|
||||
}
|
||||
|
||||
return sorted, nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package sorts
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkTopological(b *testing.B) {
|
||||
type Item struct {
|
||||
ID int
|
||||
Depends []int
|
||||
}
|
||||
|
||||
var items = []Item{
|
||||
{ID: 2, Depends: []int{4}},
|
||||
{ID: 1, Depends: []int{2, 3}},
|
||||
{ID: 3, Depends: []int{4}},
|
||||
{ID: 4, Depends: []int{5}},
|
||||
{ID: 5, Depends: []int{}},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Topological(items, func(item Item) int {
|
||||
return item.ID
|
||||
}, func(item Item) []int {
|
||||
return item.Depends
|
||||
})
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package sorts_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/sorts"
|
||||
)
|
||||
|
||||
func ExampleTopological() {
|
||||
type Item struct {
|
||||
ID int
|
||||
Depends []int
|
||||
}
|
||||
|
||||
var items = []Item{
|
||||
{ID: 2, Depends: []int{4}},
|
||||
{ID: 1, Depends: []int{2, 3}},
|
||||
{ID: 3, Depends: []int{4}},
|
||||
{ID: 4, Depends: []int{5}},
|
||||
{ID: 5, Depends: []int{}},
|
||||
}
|
||||
|
||||
var sorted, err = sorts.Topological(items, func(item Item) int {
|
||||
return item.ID
|
||||
}, func(item Item) []int {
|
||||
return item.Depends
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range sorted {
|
||||
fmt.Println(item.ID, "|", item.Depends)
|
||||
}
|
||||
// Output:
|
||||
// 1 | [2 3]
|
||||
// 2 | [4]
|
||||
// 3 | [4]
|
||||
// 4 | [5]
|
||||
// 5 | []
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package sorts_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/sorts"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTopological(t *testing.T) {
|
||||
type Item struct {
|
||||
ID int
|
||||
Depends []int
|
||||
}
|
||||
|
||||
var items = []Item{
|
||||
{ID: 2, Depends: []int{4}},
|
||||
{ID: 1, Depends: []int{2, 3}},
|
||||
{ID: 3, Depends: []int{4}},
|
||||
{ID: 4, Depends: []int{5}},
|
||||
{ID: 5, Depends: []int{}},
|
||||
}
|
||||
|
||||
var sorted, err = sorts.Topological(items, func(item Item) int {
|
||||
return item.ID
|
||||
}, func(item Item) []int {
|
||||
return item.Depends
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range sorted {
|
||||
t.Log(item.ID, "|", item.Depends)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue