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  }