github.com/opentofu/opentofu@v1.7.1/internal/dag/tarjan.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package dag 7 8 // StronglyConnected returns the list of strongly connected components 9 // within the Graph g. This information is primarily used by this package 10 // for cycle detection, but strongly connected components have widespread 11 // use. 12 func StronglyConnected(g *Graph) [][]Vertex { 13 vs := g.Vertices() 14 acct := sccAcct{ 15 NextIndex: 1, 16 VertexIndex: make(map[Vertex]int, len(vs)), 17 } 18 for _, v := range vs { 19 // Recurse on any non-visited nodes 20 if acct.VertexIndex[v] == 0 { 21 stronglyConnected(&acct, g, v) 22 } 23 } 24 return acct.SCC 25 } 26 27 func stronglyConnected(acct *sccAcct, g *Graph, v Vertex) int { 28 // Initial vertex visit 29 index := acct.visit(v) 30 minIdx := index 31 32 for _, raw := range g.downEdgesNoCopy(v) { 33 target := raw.(Vertex) 34 targetIdx := acct.VertexIndex[target] 35 36 // Recurse on successor if not yet visited 37 if targetIdx == 0 { 38 minIdx = min(minIdx, stronglyConnected(acct, g, target)) 39 } else if acct.inStack(target) { 40 // Check if the vertex is in the stack 41 minIdx = min(minIdx, targetIdx) 42 } 43 } 44 45 // Pop the strongly connected components off the stack if 46 // this is a root vertex 47 if index == minIdx { 48 var scc []Vertex 49 for { 50 v2 := acct.pop() 51 scc = append(scc, v2) 52 if v2 == v { 53 break 54 } 55 } 56 57 acct.SCC = append(acct.SCC, scc) 58 } 59 60 return minIdx 61 } 62 63 func min(a, b int) int { 64 if a <= b { 65 return a 66 } 67 return b 68 } 69 70 // sccAcct is used ot pass around accounting information for 71 // the StronglyConnectedComponents algorithm 72 type sccAcct struct { 73 NextIndex int 74 VertexIndex map[Vertex]int 75 Stack []Vertex 76 SCC [][]Vertex 77 } 78 79 // visit assigns an index and pushes a vertex onto the stack 80 func (s *sccAcct) visit(v Vertex) int { 81 idx := s.NextIndex 82 s.VertexIndex[v] = idx 83 s.NextIndex++ 84 s.push(v) 85 return idx 86 } 87 88 // push adds a vertex to the stack 89 func (s *sccAcct) push(n Vertex) { 90 s.Stack = append(s.Stack, n) 91 } 92 93 // pop removes a vertex from the stack 94 func (s *sccAcct) pop() Vertex { 95 n := len(s.Stack) 96 if n == 0 { 97 return nil 98 } 99 vertex := s.Stack[n-1] 100 s.Stack = s.Stack[:n-1] 101 return vertex 102 } 103 104 // inStack checks if a vertex is in the stack 105 func (s *sccAcct) inStack(needle Vertex) bool { 106 for _, n := range s.Stack { 107 if n == needle { 108 return true 109 } 110 } 111 return false 112 }