mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
111 lines
2.3 KiB
Go
111 lines
2.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package dag
|
|
|
|
// StronglyConnected returns the list of strongly connected components
|
|
// within the Graph g. This information is primarily used by this package
|
|
// for cycle detection, but strongly connected components have widespread
|
|
// use.
|
|
func StronglyConnected(g *Graph) [][]Vertex {
|
|
vs := g.Vertices()
|
|
acct := sccAcct{
|
|
NextIndex: 1,
|
|
VertexIndex: make(map[Vertex]int, len(vs)),
|
|
}
|
|
for _, v := range vs {
|
|
// Recurse on any non-visited nodes
|
|
if acct.VertexIndex[v] == 0 {
|
|
stronglyConnected(&acct, g, v)
|
|
}
|
|
}
|
|
return acct.SCC
|
|
}
|
|
|
|
func stronglyConnected(acct *sccAcct, g *Graph, v Vertex) int {
|
|
// Initial vertex visit
|
|
index := acct.visit(v)
|
|
minIdx := index
|
|
|
|
for _, raw := range g.downEdgesNoCopy(v) {
|
|
target := raw.(Vertex)
|
|
targetIdx := acct.VertexIndex[target]
|
|
|
|
// Recurse on successor if not yet visited
|
|
if targetIdx == 0 {
|
|
minIdx = min(minIdx, stronglyConnected(acct, g, target))
|
|
} else if acct.inStack(target) {
|
|
// Check if the vertex is in the stack
|
|
minIdx = min(minIdx, targetIdx)
|
|
}
|
|
}
|
|
|
|
// Pop the strongly connected components off the stack if
|
|
// this is a root vertex
|
|
if index == minIdx {
|
|
var scc []Vertex
|
|
for {
|
|
v2 := acct.pop()
|
|
scc = append(scc, v2)
|
|
if v2 == v {
|
|
break
|
|
}
|
|
}
|
|
|
|
acct.SCC = append(acct.SCC, scc)
|
|
}
|
|
|
|
return minIdx
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a <= b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// sccAcct is used ot pass around accounting information for
|
|
// the StronglyConnectedComponents algorithm
|
|
type sccAcct struct {
|
|
NextIndex int
|
|
VertexIndex map[Vertex]int
|
|
Stack []Vertex
|
|
SCC [][]Vertex
|
|
}
|
|
|
|
// visit assigns an index and pushes a vertex onto the stack
|
|
func (s *sccAcct) visit(v Vertex) int {
|
|
idx := s.NextIndex
|
|
s.VertexIndex[v] = idx
|
|
s.NextIndex++
|
|
s.push(v)
|
|
return idx
|
|
}
|
|
|
|
// push adds a vertex to the stack
|
|
func (s *sccAcct) push(n Vertex) {
|
|
s.Stack = append(s.Stack, n)
|
|
}
|
|
|
|
// pop removes a vertex from the stack
|
|
func (s *sccAcct) pop() Vertex {
|
|
n := len(s.Stack)
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
vertex := s.Stack[n-1]
|
|
s.Stack = s.Stack[:n-1]
|
|
return vertex
|
|
}
|
|
|
|
// inStack checks if a vertex is in the stack
|
|
func (s *sccAcct) inStack(needle Vertex) bool {
|
|
for _, n := range s.Stack {
|
|
if n == needle {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|