github.com/svenhamers/terraform@v0.11.12-beta1/digraph/tarjan.go (about)

     1  package digraph
     2  
     3  // sccAcct is used ot pass around accounting information for
     4  // the StronglyConnectedComponents algorithm
     5  type sccAcct struct {
     6  	ExcludeSingle bool
     7  	NextIndex     int
     8  	NodeIndex     map[Node]int
     9  	Stack         []Node
    10  	SCC           [][]Node
    11  }
    12  
    13  // visit assigns an index and pushes a node onto the stack
    14  func (s *sccAcct) visit(n Node) int {
    15  	idx := s.NextIndex
    16  	s.NodeIndex[n] = idx
    17  	s.NextIndex++
    18  	s.push(n)
    19  	return idx
    20  }
    21  
    22  // push adds a node to the stack
    23  func (s *sccAcct) push(n Node) {
    24  	s.Stack = append(s.Stack, n)
    25  }
    26  
    27  // pop removes a node from the stack
    28  func (s *sccAcct) pop() Node {
    29  	n := len(s.Stack)
    30  	if n == 0 {
    31  		return nil
    32  	}
    33  	node := s.Stack[n-1]
    34  	s.Stack = s.Stack[:n-1]
    35  	return node
    36  }
    37  
    38  // inStack checks if a node is in the stack
    39  func (s *sccAcct) inStack(needle Node) bool {
    40  	for _, n := range s.Stack {
    41  		if n == needle {
    42  			return true
    43  		}
    44  	}
    45  	return false
    46  }
    47  
    48  // StronglyConnectedComponents implements Tarjan's algorithm to
    49  // find all the strongly connected components in a graph. This can
    50  // be used to detected any cycles in a graph, as well as which nodes
    51  // partipate in those cycles. excludeSingle is used to exclude strongly
    52  // connected components of size one.
    53  func StronglyConnectedComponents(nodes []Node, excludeSingle bool) [][]Node {
    54  	acct := sccAcct{
    55  		ExcludeSingle: excludeSingle,
    56  		NextIndex:     1,
    57  		NodeIndex:     make(map[Node]int, len(nodes)),
    58  	}
    59  	for _, node := range nodes {
    60  		// Recurse on any non-visited nodes
    61  		if acct.NodeIndex[node] == 0 {
    62  			stronglyConnected(&acct, node)
    63  		}
    64  	}
    65  	return acct.SCC
    66  }
    67  
    68  func stronglyConnected(acct *sccAcct, node Node) int {
    69  	// Initial node visit
    70  	index := acct.visit(node)
    71  	minIdx := index
    72  
    73  	for _, edge := range node.Edges() {
    74  		target := edge.Tail()
    75  		targetIdx := acct.NodeIndex[target]
    76  
    77  		// Recurse on successor if not yet visited
    78  		if targetIdx == 0 {
    79  			minIdx = min(minIdx, stronglyConnected(acct, target))
    80  
    81  		} else if acct.inStack(target) {
    82  			// Check if the node is in the stack
    83  			minIdx = min(minIdx, targetIdx)
    84  		}
    85  	}
    86  
    87  	// Pop the strongly connected components off the stack if
    88  	// this is a root node
    89  	if index == minIdx {
    90  		var scc []Node
    91  		for {
    92  			n := acct.pop()
    93  			scc = append(scc, n)
    94  			if n == node {
    95  				break
    96  			}
    97  		}
    98  		if !(acct.ExcludeSingle && len(scc) == 1) {
    99  			acct.SCC = append(acct.SCC, scc)
   100  		}
   101  	}
   102  
   103  	return minIdx
   104  }
   105  
   106  func min(a, b int) int {
   107  	if a <= b {
   108  		return a
   109  	}
   110  	return b
   111  }