From 7a5e2c1e7e5e14c7820871adba269985d01bd129 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 11 Sep 2023 11:31:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20utils=20=E4=B8=8B=E6=96=B0=E5=A2=9E=20s?= =?UTF-8?q?orts.Topological=20=E6=8B=93=E6=89=91=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/sorts/errors.go | 7 +++ utils/sorts/topological.go | 60 +++++++++++++++++++++++ utils/sorts/topological_benchmark_test.go | 31 ++++++++++++ utils/sorts/topological_example_test.go | 41 ++++++++++++++++ utils/sorts/topological_test.go | 36 ++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 utils/sorts/errors.go create mode 100644 utils/sorts/topological.go create mode 100644 utils/sorts/topological_benchmark_test.go create mode 100644 utils/sorts/topological_example_test.go create mode 100644 utils/sorts/topological_test.go diff --git a/utils/sorts/errors.go b/utils/sorts/errors.go new file mode 100644 index 0000000..32f646d --- /dev/null +++ b/utils/sorts/errors.go @@ -0,0 +1,7 @@ +package sorts + +import "errors" + +var ( + ErrCircularDependencyDetected = errors.New("circular dependency detected") +) diff --git a/utils/sorts/topological.go b/utils/sorts/topological.go new file mode 100644 index 0000000..3535219 --- /dev/null +++ b/utils/sorts/topological.go @@ -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 +} diff --git a/utils/sorts/topological_benchmark_test.go b/utils/sorts/topological_benchmark_test.go new file mode 100644 index 0000000..a3be118 --- /dev/null +++ b/utils/sorts/topological_benchmark_test.go @@ -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 + } + } +} diff --git a/utils/sorts/topological_example_test.go b/utils/sorts/topological_example_test.go new file mode 100644 index 0000000..20ec647 --- /dev/null +++ b/utils/sorts/topological_example_test.go @@ -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 | [] +} diff --git a/utils/sorts/topological_test.go b/utils/sorts/topological_test.go new file mode 100644 index 0000000..504f7c9 --- /dev/null +++ b/utils/sorts/topological_test.go @@ -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) + } +}