package dag12// WalkFunc is a function that gets invoked when walking a Graph. Walking will3// stop if WalkFunc returns a non-nil error.4type WalkFunc func(n Node) error56// Walk performs a depth-first walk of outgoing edges for all nodes in start,7// invoking the provided fn for each node. Walk returns the error returned by8// fn.9//10// Nodes unreachable from start will not be passed to fn.11func Walk(g *Graph, start []Node, fn WalkFunc) error {12var (13visited = make(nodeSet)14unchecked = make([]Node, 0, len(start))15)1617// Prefill the set of unchecked nodes with our start set.18unchecked = append(unchecked, start...)1920// Iterate through unchecked nodes, visiting each in turn and adding outgoing21// edges to the unchecked list until all reachable nodes have been processed.22for len(unchecked) > 0 {23check := unchecked[len(unchecked)-1]24unchecked = unchecked[:len(unchecked)-1]2526if visited.Has(check) {27continue28}29visited.Add(check)3031if err := fn(check); err != nil {32return err33}3435for n := range g.outEdges[check] {36unchecked = append(unchecked, n)37}38}3940return nil41}4243// WalkReverse performs a depth-first walk of incoming edges for all nodes in44// start, invoking the provided fn for each node. Walk returns the error45// returned by fn.46//47// Nodes unreachable from start will not be passed to fn.48func WalkReverse(g *Graph, start []Node, fn WalkFunc) error {49var (50visited = make(nodeSet)51unchecked = make([]Node, 0, len(start))52)5354// Prefill the set of unchecked nodes with our start set.55unchecked = append(unchecked, start...)5657// Iterate through unchecked nodes, visiting each in turn and adding incoming58// edges to the unchecked list until all reachable nodes have been processed.59for len(unchecked) > 0 {60check := unchecked[len(unchecked)-1]61unchecked = unchecked[:len(unchecked)-1]6263if visited.Has(check) {64continue65}66visited.Add(check)6768if err := fn(check); err != nil {69return err70}7172for n := range g.inEdges[check] {73unchecked = append(unchecked, n)74}75}7677return nil78}7980// WalkTopological performs a topological walk of all nodes in start in81// dependency order: a node will not be visited until its outgoing edges are82// visited first.83//84// Nodes will not be passed to fn if they are not reachable from start or if85// not all of their outgoing edges are reachable from start.86func WalkTopological(g *Graph, start []Node, fn WalkFunc) error {87// NOTE(rfratto): WalkTopological is an implementation of Kahn's algorithm88// which leaves g unmodified.8990var (91visited = make(nodeSet)92unchecked = make([]Node, 0, len(start))9394remainingDeps = make(map[Node]int)95)9697// Pre-fill the set of nodes to check from the start list.98unchecked = append(unchecked, start...)99100for len(unchecked) > 0 {101check := unchecked[len(unchecked)-1]102unchecked = unchecked[:len(unchecked)-1]103104if visited.Has(check) {105continue106}107visited.Add(check)108109if err := fn(check); err != nil {110return err111}112113// Iterate through the incoming edges to check and queue nodes if we're the114// last edge to be walked.115for n := range g.inEdges[check] {116// remainingDeps starts with the number of edges, and we subtract one for117// each outgoing edge that's visited.118if _, ok := remainingDeps[n]; !ok {119remainingDeps[n] = len(g.outEdges[n])120}121remainingDeps[n]--122123// Only enqueue the incoming edge once all of its outgoing edges have124// been consumed. This prevents it from being visited before its125// dependencies.126if remainingDeps[n] == 0 {127unchecked = append(unchecked, n)128}129}130}131132return nil133}134135136