opentofu/internal/dag/tarjan.go
Martin Atkins 70eebe3521 Move dag/ to internal/dag/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

108 lines
2.2 KiB
Go

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
}