114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
package digraph
|
|
|
|
// DepthFirstWalk performs a depth-first traversal of the nodes
|
|
// that can be reached from the initial input set. The callback is
|
|
// invoked for each visited node, and may return false to prevent
|
|
// vising any children of the current node
|
|
func DepthFirstWalk(node Node, cb func(n Node) bool) {
|
|
frontier := []Node{node}
|
|
seen := make(map[Node]struct{})
|
|
for len(frontier) > 0 {
|
|
// Pop the current node
|
|
n := len(frontier)
|
|
current := frontier[n-1]
|
|
frontier = frontier[:n-1]
|
|
|
|
// Check for potential cycle
|
|
if _, ok := seen[current]; ok {
|
|
continue
|
|
}
|
|
seen[current] = struct{}{}
|
|
|
|
// Visit with the callback
|
|
if !cb(current) {
|
|
continue
|
|
}
|
|
|
|
// Add any new edges to visit, in reverse order
|
|
edges := current.Edges()
|
|
for i := len(edges) - 1; i >= 0; i-- {
|
|
frontier = append(frontier, edges[i].Tail())
|
|
}
|
|
}
|
|
}
|
|
|
|
// FilterDegree returns only the nodes with the desired
|
|
// degree. This can be used with OutDegree or InDegree
|
|
func FilterDegree(degree int, degrees map[Node]int) []Node {
|
|
var matching []Node
|
|
for n, d := range degrees {
|
|
if d == degree {
|
|
matching = append(matching, n)
|
|
}
|
|
}
|
|
return matching
|
|
}
|
|
|
|
// InDegree is used to compute the in-degree of nodes
|
|
func InDegree(nodes []Node) map[Node]int {
|
|
degree := make(map[Node]int, len(nodes))
|
|
for _, n := range nodes {
|
|
if _, ok := degree[n]; !ok {
|
|
degree[n] = 0
|
|
}
|
|
for _, e := range n.Edges() {
|
|
degree[e.Tail()]++
|
|
}
|
|
}
|
|
return degree
|
|
}
|
|
|
|
// OutDegree is used to compute the in-degree of nodes
|
|
func OutDegree(nodes []Node) map[Node]int {
|
|
degree := make(map[Node]int, len(nodes))
|
|
for _, n := range nodes {
|
|
degree[n] = len(n.Edges())
|
|
}
|
|
return degree
|
|
}
|
|
|
|
// Sinks is used to get the nodes with out-degree of 0
|
|
func Sinks(nodes []Node) []Node {
|
|
return FilterDegree(0, OutDegree(nodes))
|
|
}
|
|
|
|
// Sources is used to get the nodes with in-degree of 0
|
|
func Sources(nodes []Node) []Node {
|
|
return FilterDegree(0, InDegree(nodes))
|
|
}
|
|
|
|
// Unreachable starts at a given start node, performs
|
|
// a DFS from there, and returns the set of unreachable nodes.
|
|
func Unreachable(start Node, nodes []Node) []Node {
|
|
// DFS from the start ndoe
|
|
frontier := []Node{start}
|
|
seen := make(map[Node]struct{})
|
|
for len(frontier) > 0 {
|
|
// Pop the current node
|
|
n := len(frontier)
|
|
current := frontier[n-1]
|
|
frontier = frontier[:n-1]
|
|
|
|
// Check for potential cycle
|
|
if _, ok := seen[current]; ok {
|
|
continue
|
|
}
|
|
seen[current] = struct{}{}
|
|
|
|
// Add any new edges to visit, in reverse order
|
|
edges := current.Edges()
|
|
for i := len(edges) - 1; i >= 0; i-- {
|
|
frontier = append(frontier, edges[i].Tail())
|
|
}
|
|
}
|
|
|
|
// Check for any unseen nodes
|
|
var unseen []Node
|
|
for _, node := range nodes {
|
|
if _, ok := seen[node]; !ok {
|
|
unseen = append(unseen, node)
|
|
}
|
|
}
|
|
return unseen
|
|
}
|