mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-29 10:21:01 -06:00
112 lines
2.4 KiB
Go
112 lines
2.4 KiB
Go
|
package digraph
|
||
|
|
||
|
// sccAcct is used ot pass around accounting information for
|
||
|
// the StronglyConnectedComponents algorithm
|
||
|
type sccAcct struct {
|
||
|
ExcludeSingle bool
|
||
|
NextIndex int
|
||
|
NodeIndex map[Node]int
|
||
|
Stack []Node
|
||
|
SCC [][]Node
|
||
|
}
|
||
|
|
||
|
// visit assigns an index and pushes a node onto the stack
|
||
|
func (s *sccAcct) visit(n Node) int {
|
||
|
idx := s.NextIndex
|
||
|
s.NodeIndex[n] = idx
|
||
|
s.NextIndex++
|
||
|
s.push(n)
|
||
|
return idx
|
||
|
}
|
||
|
|
||
|
// push adds a node to the stack
|
||
|
func (s *sccAcct) push(n Node) {
|
||
|
s.Stack = append(s.Stack, n)
|
||
|
}
|
||
|
|
||
|
// pop removes a node from the stack
|
||
|
func (s *sccAcct) pop() Node {
|
||
|
n := len(s.Stack)
|
||
|
if n == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
node := s.Stack[n-1]
|
||
|
s.Stack = s.Stack[:n-1]
|
||
|
return node
|
||
|
}
|
||
|
|
||
|
// inStack checks if a node is in the stack
|
||
|
func (s *sccAcct) inStack(needle Node) bool {
|
||
|
for _, n := range s.Stack {
|
||
|
if n == needle {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// StronglyConnectedComponents implements Tarjan's algorithm to
|
||
|
// find all the strongly connected components in a graph. This can
|
||
|
// be used to detected any cycles in a graph, as well as which nodes
|
||
|
// partipate in those cycles. excludeSingle is used to exclude strongly
|
||
|
// connected components of size one.
|
||
|
func StronglyConnectedComponents(nodes []Node, excludeSingle bool) [][]Node {
|
||
|
acct := sccAcct{
|
||
|
ExcludeSingle: excludeSingle,
|
||
|
NextIndex: 1,
|
||
|
NodeIndex: make(map[Node]int, len(nodes)),
|
||
|
}
|
||
|
for _, node := range nodes {
|
||
|
// Recurse on any non-visited nodes
|
||
|
if acct.NodeIndex[node] == 0 {
|
||
|
stronglyConnected(&acct, node)
|
||
|
}
|
||
|
}
|
||
|
return acct.SCC
|
||
|
}
|
||
|
|
||
|
func stronglyConnected(acct *sccAcct, node Node) int {
|
||
|
// Initial node visit
|
||
|
index := acct.visit(node)
|
||
|
minIdx := index
|
||
|
|
||
|
for _, edge := range node.Edges() {
|
||
|
target := edge.Tail()
|
||
|
targetIdx := acct.NodeIndex[target]
|
||
|
|
||
|
// Recurse on successor if not yet visited
|
||
|
if targetIdx == 0 {
|
||
|
minIdx = min(minIdx, stronglyConnected(acct, target))
|
||
|
|
||
|
} else if acct.inStack(target) {
|
||
|
// Check if the node is in the stack
|
||
|
minIdx = min(minIdx, targetIdx)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Pop the strongly connected components off the stack if
|
||
|
// this is a root node
|
||
|
if index == minIdx {
|
||
|
var scc []Node
|
||
|
for {
|
||
|
n := acct.pop()
|
||
|
scc = append(scc, n)
|
||
|
if n == node {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !(acct.ExcludeSingle && len(scc) == 1) {
|
||
|
acct.SCC = append(acct.SCC, scc)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return minIdx
|
||
|
}
|
||
|
|
||
|
func min(a, b int) int {
|
||
|
if a <= b {
|
||
|
return a
|
||
|
}
|
||
|
return b
|
||
|
}
|