github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/utils/sort.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package utils provides generic utility functions.
     5  package utils
     6  
     7  import "fmt"
     8  
     9  // Dependency is an interface that represents a node in a list of dependencies.
    10  type Dependency interface {
    11  	Name() string
    12  	Dependencies() []string
    13  }
    14  
    15  // SortDependencies performs a topological sort on a dependency graph and
    16  // returns a slice of the nodes in order of their precedence.
    17  // The input data is a map of nodes to a slice of its dependencies.
    18  //
    19  // E.g:
    20  // A depends on B & C, B depends on C and C has no dependencies:
    21  // {"A": ["B", "C"], "B": ["C"], "C": []string{}}
    22  //
    23  // Note sort order is dependent on the slice order of the input data for
    24  // nodes with the same in-degree (i.e. the same number of dependencies).
    25  func SortDependencies(data []Dependency) ([]string, error) {
    26  	// Initialize the in-degree and out-degree maps.
    27  	inDegree := make(map[string]int)
    28  	outDegree := make(map[string][]string)
    29  
    30  	// Populate the in-degree and out-degree maps.
    31  	for _, d := range data {
    32  		outDegree[d.Name()] = d.Dependencies()
    33  		inDegree[d.Name()] = 0
    34  	}
    35  	for _, deps := range data {
    36  		for _, d := range deps.Dependencies() {
    37  			inDegree[d]++
    38  		}
    39  	}
    40  
    41  	// Initialize the queue and the result list.
    42  	queue := make([]string, 0)
    43  	result := make([]string, 0)
    44  
    45  	// Enqueue all nodes with zero in-degree.
    46  	for _, d := range data {
    47  		if inDegree[d.Name()] == 0 {
    48  			queue = append(queue, d.Name())
    49  		}
    50  	}
    51  
    52  	// Process the queue.
    53  	for len(queue) > 0 {
    54  		// Dequeue a node from the queue.
    55  		node := queue[0]
    56  		queue = queue[1:]
    57  
    58  		// Add the node to the result list.
    59  		result = append([]string{node}, result...)
    60  
    61  		// Decrement the in-degree of all outgoing neighbors.
    62  		for _, neighbor := range outDegree[node] {
    63  			inDegree[neighbor]--
    64  			// If the neighbor has zero in-degree, enqueue it.
    65  			if inDegree[neighbor] == 0 {
    66  				queue = append(queue, neighbor)
    67  			}
    68  		}
    69  	}
    70  
    71  	// If there are still nodes with non-zero in-degree, there is a cycle in the graph.
    72  	// Return an empty result list to indicate this.
    73  	for _, degree := range inDegree {
    74  		if degree > 0 {
    75  			return result, fmt.Errorf("dependency cycle detected")
    76  		}
    77  	}
    78  
    79  	// Return the result list.
    80  	return result, nil
    81  }