github.com/erda-project/erda-infra@v1.0.9/base/servicehub/dependency-graph/dependency_graph.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package graph 16 17 // reference http://dnaeon.github.io/dependency-graph-resolution-algorithm-in-go/ 18 19 import ( 20 "errors" 21 "fmt" 22 ) 23 24 // Node represents a single node in the graph with it's dependencies 25 type Node struct { 26 // Name of the node 27 Name string 28 29 // Dependencies of the node 30 Deps []string 31 } 32 33 func (n *Node) String() string { 34 if len(n.Deps) > 0 { 35 return fmt.Sprintf("%s -> %s", n.Name, n.Deps) 36 } 37 return n.Name 38 } 39 40 // NewNode creates a new node 41 func NewNode(name string, deps ...string) *Node { 42 n := &Node{ 43 Name: name, 44 Deps: deps, 45 } 46 return n 47 } 48 49 // Graph dependency graph 50 type Graph []*Node 51 52 // Display the dependency graph 53 func (g Graph) Display() { 54 for _, node := range g { 55 if len(node.Deps) <= 0 { 56 fmt.Println(node.Name) 57 } else { 58 for _, dep := range node.Deps { 59 fmt.Printf("%s -> %s\n", node.Name, dep) 60 } 61 } 62 } 63 } 64 65 // Resolve the dependency graph 66 func Resolve(graph Graph) (Graph, error) { 67 // A map containing the node names and the actual node object 68 nodeNames := make(map[string]*Node) 69 70 // A map containing the nodes and their dependencies 71 nodeDependencies := make(map[string]map[string]struct{}) 72 73 // Populate the maps 74 for _, node := range graph { 75 nodeNames[node.Name] = node 76 77 dependencySet := make(map[string]struct{}) 78 for _, dep := range node.Deps { 79 dependencySet[dep] = struct{}{} 80 } 81 nodeDependencies[node.Name] = dependencySet 82 } 83 84 // Iteratively find and remove nodes from the graph which have no dependencies. 85 // If at some point there are still nodes in the graph and we cannot find 86 // nodes without dependencies, that means we have a circular dependency 87 var resolved Graph 88 for len(nodeDependencies) != 0 { 89 // Get all nodes from the graph which have no dependencies 90 readySet := make(map[string]struct{}) 91 for name, deps := range nodeDependencies { 92 if len(deps) == 0 { 93 readySet[name] = struct{}{} 94 } 95 } 96 97 // If there aren't any ready nodes, then we have a cicular dependency 98 if len(readySet) == 0 { 99 var g Graph 100 for name := range nodeDependencies { 101 g = append(g, nodeNames[name]) 102 } 103 return g, errors.New("Circular dependency found") 104 } 105 106 // Remove the ready nodes and add them to the resolved graph 107 for name := range readySet { 108 delete(nodeDependencies, name) 109 resolved = append(resolved, nodeNames[name]) 110 } 111 112 // Also make sure to remove the ready nodes from the 113 // remaining node dependencies as well 114 for name, deps := range nodeDependencies { 115 diff := make(map[string]struct{}) 116 for dep := range deps { 117 if _, ok := readySet[dep]; !ok { 118 diff[dep] = struct{}{} 119 } 120 } 121 nodeDependencies[name] = diff 122 } 123 } 124 125 return resolved, nil 126 }