412 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			412 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
// The digraph command performs queries over unlabelled directed graphs
 | 
						|
// represented in text form.  It is intended to integrate nicely with
 | 
						|
// typical UNIX command pipelines.
 | 
						|
//
 | 
						|
// Since directed graphs (import graphs, reference graphs, call graphs,
 | 
						|
// etc) often arise during software tool development and debugging, this
 | 
						|
// command is included in the go.tools repository.
 | 
						|
//
 | 
						|
// TODO(adonovan):
 | 
						|
// - support input files other than stdin
 | 
						|
// - suport alternative formats (AT&T GraphViz, CSV, etc),
 | 
						|
//   a comment syntax, etc.
 | 
						|
// - allow queries to nest, like Blaze query language.
 | 
						|
//
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
const Usage = `digraph: queries over directed graphs in text form.
 | 
						|
 | 
						|
Graph format:
 | 
						|
 | 
						|
  Each line contains zero or more whitespace-separated fields.
 | 
						|
  Each field declares a node, and if there are more than one,
 | 
						|
  an edge from the first to each subsequent one.
 | 
						|
  The graph is provided on the standard input.
 | 
						|
 | 
						|
  For instance, the following (acyclic) graph specifies a partial order
 | 
						|
  among the subtasks of getting dressed:
 | 
						|
 | 
						|
	% cat clothes.txt
 | 
						|
	socks shoes
 | 
						|
	shorts pants
 | 
						|
	pants belt shoes
 | 
						|
	shirt tie sweater
 | 
						|
	sweater jacket
 | 
						|
	hat
 | 
						|
 | 
						|
  The line "shirt tie sweater" indicates the two edges shirt -> tie and
 | 
						|
  shirt -> sweater, not shirt -> tie -> sweater.
 | 
						|
 | 
						|
Supported queries:
 | 
						|
 | 
						|
  nodes
 | 
						|
	the set of all nodes
 | 
						|
  degree
 | 
						|
	the in-degree and out-degree of each node.
 | 
						|
  preds <label> ...
 | 
						|
	the set of immediate predecessors of the specified nodes
 | 
						|
  succs <label> ...
 | 
						|
	the set of immediate successors of the specified nodes
 | 
						|
  forward <label> ...
 | 
						|
	the set of nodes transitively reachable from the specified nodes
 | 
						|
  reverse <label> ...
 | 
						|
	the set of nodes that transitively reach the specified nodes
 | 
						|
  somepath <label> <label>
 | 
						|
	the list of nodes on some arbitrary path from the first node to the second
 | 
						|
  allpaths <label> <label>
 | 
						|
	the set of nodes on all paths from the first node to the second
 | 
						|
  sccs
 | 
						|
	all strongly connected components (one per line)
 | 
						|
  scc <label>
 | 
						|
	the set of nodes nodes strongly connected to the specified one
 | 
						|
 | 
						|
Example usage:
 | 
						|
 | 
						|
   Show the transitive closure of imports of the digraph tool itself:
 | 
						|
   % go list -f '{{.ImportPath}}{{.Imports}}' ... | tr '[]' '  ' |
 | 
						|
         digraph forward code.google.com/p/go.tools/cmd/digraph
 | 
						|
 | 
						|
   Show which clothes (see above) must be donned before a jacket:
 | 
						|
   %  digraph reverse jacket <clothes.txt
 | 
						|
 | 
						|
`
 | 
						|
 | 
						|
func main() {
 | 
						|
	flag.Parse()
 | 
						|
 | 
						|
	args := flag.Args()
 | 
						|
	if len(args) == 0 {
 | 
						|
		fmt.Println(Usage)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err := digraph(args[0], args[1:]); err != nil {
 | 
						|
		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type nodelist []string
 | 
						|
 | 
						|
func (l nodelist) println(sep string) {
 | 
						|
	for i, label := range l {
 | 
						|
		if i > 0 {
 | 
						|
			fmt.Fprint(stdout, sep)
 | 
						|
		}
 | 
						|
		fmt.Fprint(stdout, label)
 | 
						|
	}
 | 
						|
	fmt.Fprintln(stdout)
 | 
						|
}
 | 
						|
 | 
						|
type nodeset map[string]bool
 | 
						|
 | 
						|
func (s nodeset) sort() nodelist {
 | 
						|
	labels := make(nodelist, len(s))
 | 
						|
	var i int
 | 
						|
	for label := range s {
 | 
						|
		labels[i] = label
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	sort.Strings(labels)
 | 
						|
	return labels
 | 
						|
}
 | 
						|
 | 
						|
func (s nodeset) addAll(x nodeset) {
 | 
						|
	for label := range x {
 | 
						|
		s[label] = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// A graph maps nodes to the non-nil set of their immediate successors.
 | 
						|
type graph map[string]nodeset
 | 
						|
 | 
						|
func (g graph) addNode(label string) nodeset {
 | 
						|
	edges := g[label]
 | 
						|
	if edges == nil {
 | 
						|
		edges = make(nodeset)
 | 
						|
		g[label] = edges
 | 
						|
	}
 | 
						|
	return edges
 | 
						|
}
 | 
						|
 | 
						|
func (g graph) addEdges(from string, to ...string) {
 | 
						|
	edges := g.addNode(from)
 | 
						|
	for _, to := range to {
 | 
						|
		g.addNode(to)
 | 
						|
		edges[to] = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (g graph) reachableFrom(roots nodeset) nodeset {
 | 
						|
	seen := make(nodeset)
 | 
						|
	var visit func(label string)
 | 
						|
	visit = func(label string) {
 | 
						|
		if !seen[label] {
 | 
						|
			seen[label] = true
 | 
						|
			for e := range g[label] {
 | 
						|
				visit(e)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for root := range roots {
 | 
						|
		visit(root)
 | 
						|
	}
 | 
						|
	return seen
 | 
						|
}
 | 
						|
 | 
						|
func (g graph) transpose() graph {
 | 
						|
	rev := make(graph)
 | 
						|
	for label, edges := range g {
 | 
						|
		rev.addNode(label)
 | 
						|
		for succ := range edges {
 | 
						|
			rev.addEdges(succ, label)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return rev
 | 
						|
}
 | 
						|
 | 
						|
func (g graph) sccs() []nodeset {
 | 
						|
	// Kosaraju's algorithm---Tarjan is overkill here.
 | 
						|
 | 
						|
	// Forward pass.
 | 
						|
	S := make(nodelist, 0, len(g)) // postorder stack
 | 
						|
	seen := make(nodeset)
 | 
						|
	var visit func(label string)
 | 
						|
	visit = func(label string) {
 | 
						|
		if !seen[label] {
 | 
						|
			seen[label] = true
 | 
						|
			for e := range g[label] {
 | 
						|
				visit(e)
 | 
						|
			}
 | 
						|
			S = append(S, label)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for label := range g {
 | 
						|
		visit(label)
 | 
						|
	}
 | 
						|
 | 
						|
	// Reverse pass.
 | 
						|
	rev := g.transpose()
 | 
						|
	var scc nodeset
 | 
						|
	seen = make(nodeset)
 | 
						|
	var rvisit func(label string)
 | 
						|
	rvisit = func(label string) {
 | 
						|
		if !seen[label] {
 | 
						|
			seen[label] = true
 | 
						|
			scc[label] = true
 | 
						|
			for e := range rev[label] {
 | 
						|
				rvisit(e)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	var sccs []nodeset
 | 
						|
	for len(S) > 0 {
 | 
						|
		top := S[len(S)-1]
 | 
						|
		S = S[:len(S)-1] // pop
 | 
						|
		if !seen[top] {
 | 
						|
			scc = make(nodeset)
 | 
						|
			rvisit(top)
 | 
						|
			sccs = append(sccs, scc)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return sccs
 | 
						|
}
 | 
						|
 | 
						|
func parse(rd io.Reader) (graph, error) {
 | 
						|
	g := make(graph)
 | 
						|
 | 
						|
	in := bufio.NewScanner(rd)
 | 
						|
	for in.Scan() {
 | 
						|
		words := strings.Fields(in.Text())
 | 
						|
		if len(words) > 0 {
 | 
						|
			g.addEdges(words[0], words[1:]...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := in.Err(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return g, nil
 | 
						|
}
 | 
						|
 | 
						|
var stdin io.Reader = os.Stdin
 | 
						|
var stdout io.Writer = os.Stdout
 | 
						|
 | 
						|
func digraph(cmd string, args []string) error {
 | 
						|
	// Parse the input graph.
 | 
						|
	g, err := parse(stdin)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse the command line.
 | 
						|
	switch cmd {
 | 
						|
	case "nodes":
 | 
						|
		if len(args) != 0 {
 | 
						|
			return fmt.Errorf("usage: nodes")
 | 
						|
		}
 | 
						|
		nodes := make(nodeset)
 | 
						|
		for label := range g {
 | 
						|
			nodes[label] = true
 | 
						|
		}
 | 
						|
		nodes.sort().println("\n")
 | 
						|
 | 
						|
	case "degree":
 | 
						|
		if len(args) != 0 {
 | 
						|
			return fmt.Errorf("usage: degree")
 | 
						|
		}
 | 
						|
		nodes := make(nodeset)
 | 
						|
		for label := range g {
 | 
						|
			nodes[label] = true
 | 
						|
		}
 | 
						|
		rev := g.transpose()
 | 
						|
		for _, label := range nodes.sort() {
 | 
						|
			fmt.Fprintf(stdout, "%d\t%d\t%s\n", len(rev[label]), len(g[label]), label)
 | 
						|
		}
 | 
						|
 | 
						|
	case "succs", "preds":
 | 
						|
		if len(args) == 0 {
 | 
						|
			return fmt.Errorf("usage: %s <label> ...", cmd)
 | 
						|
		}
 | 
						|
		g := g
 | 
						|
		if cmd == "preds" {
 | 
						|
			g = g.transpose()
 | 
						|
		}
 | 
						|
		result := make(nodeset)
 | 
						|
		for _, root := range args {
 | 
						|
			edges := g[root]
 | 
						|
			if edges == nil {
 | 
						|
				return fmt.Errorf("no such node %q", root)
 | 
						|
			}
 | 
						|
			result.addAll(edges)
 | 
						|
		}
 | 
						|
		result.sort().println("\n")
 | 
						|
 | 
						|
	case "forward", "reverse":
 | 
						|
		if len(args) == 0 {
 | 
						|
			return fmt.Errorf("usage: %s <label> ...", cmd)
 | 
						|
		}
 | 
						|
		roots := make(nodeset)
 | 
						|
		for _, root := range args {
 | 
						|
			if g[root] == nil {
 | 
						|
				return fmt.Errorf("no such node %q", root)
 | 
						|
			}
 | 
						|
			roots[root] = true
 | 
						|
		}
 | 
						|
		g := g
 | 
						|
		if cmd == "reverse" {
 | 
						|
			g = g.transpose()
 | 
						|
		}
 | 
						|
		g.reachableFrom(roots).sort().println("\n")
 | 
						|
 | 
						|
	case "somepath":
 | 
						|
		if len(args) != 2 {
 | 
						|
			return fmt.Errorf("usage: somepath <from> <to>")
 | 
						|
		}
 | 
						|
		from, to := args[0], args[1]
 | 
						|
		if g[from] == nil {
 | 
						|
			return fmt.Errorf("no such 'from' node %q", from)
 | 
						|
		}
 | 
						|
		if g[to] == nil {
 | 
						|
			return fmt.Errorf("no such 'to' node %q", to)
 | 
						|
		}
 | 
						|
 | 
						|
		seen := make(nodeset)
 | 
						|
		var visit func(path nodelist, label string) bool
 | 
						|
		visit = func(path nodelist, label string) bool {
 | 
						|
			if !seen[label] {
 | 
						|
				seen[label] = true
 | 
						|
				if label == to {
 | 
						|
					append(path, label).println("\n")
 | 
						|
					return true // unwind
 | 
						|
				}
 | 
						|
				for e := range g[label] {
 | 
						|
					if visit(append(path, label), e) {
 | 
						|
						return true
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if !visit(make(nodelist, 0, 100), from) {
 | 
						|
			return fmt.Errorf("no path from %q to %q", args[0], args[1])
 | 
						|
		}
 | 
						|
 | 
						|
	case "allpaths":
 | 
						|
		if len(args) != 2 {
 | 
						|
			return fmt.Errorf("usage: allpaths <from> <to>")
 | 
						|
		}
 | 
						|
		from, to := args[0], args[1]
 | 
						|
		if g[from] == nil {
 | 
						|
			return fmt.Errorf("no such 'from' node %q", from)
 | 
						|
		}
 | 
						|
		if g[to] == nil {
 | 
						|
			return fmt.Errorf("no such 'to' node %q", to)
 | 
						|
		}
 | 
						|
 | 
						|
		seen := make(nodeset) // value of seen[x] indicates whether x is on some path to 'to'
 | 
						|
		var visit func(label string) bool
 | 
						|
		visit = func(label string) bool {
 | 
						|
			reachesTo, ok := seen[label]
 | 
						|
			if !ok {
 | 
						|
				reachesTo = label == to
 | 
						|
 | 
						|
				seen[label] = reachesTo
 | 
						|
				for e := range g[label] {
 | 
						|
					if visit(e) {
 | 
						|
						reachesTo = true
 | 
						|
					}
 | 
						|
				}
 | 
						|
				seen[label] = reachesTo
 | 
						|
			}
 | 
						|
			return reachesTo
 | 
						|
		}
 | 
						|
		if !visit(from) {
 | 
						|
			return fmt.Errorf("no path from %q to %q", from, to)
 | 
						|
		}
 | 
						|
		for label, reachesTo := range seen {
 | 
						|
			if !reachesTo {
 | 
						|
				delete(seen, label)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		seen.sort().println("\n")
 | 
						|
 | 
						|
	case "sccs":
 | 
						|
		if len(args) != 0 {
 | 
						|
			return fmt.Errorf("usage: sccs")
 | 
						|
		}
 | 
						|
		for _, scc := range g.sccs() {
 | 
						|
			scc.sort().println(" ")
 | 
						|
		}
 | 
						|
 | 
						|
	case "scc":
 | 
						|
		if len(args) != 1 {
 | 
						|
			return fmt.Errorf("usage: scc <label>")
 | 
						|
		}
 | 
						|
		label := args[0]
 | 
						|
		if g[label] == nil {
 | 
						|
			return fmt.Errorf("no such node %q", label)
 | 
						|
		}
 | 
						|
		for _, scc := range g.sccs() {
 | 
						|
			if scc[label] {
 | 
						|
				scc.sort().println("\n")
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("no such command %q", cmd)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |