dag: staticcheck
This commit is contained in:
parent
89a8624d8c
commit
8925d94387
|
@ -340,7 +340,7 @@ func BenchmarkDAG(b *testing.B) {
|
||||||
// layer B
|
// layer B
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
B := fmt.Sprintf("B%d", i)
|
B := fmt.Sprintf("B%d", i)
|
||||||
g.Add(fmt.Sprintf(B))
|
g.Add(B)
|
||||||
for j := 0; j < count; j++ {
|
for j := 0; j < count; j++ {
|
||||||
g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j)))
|
g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j)))
|
||||||
}
|
}
|
||||||
|
@ -349,7 +349,7 @@ func BenchmarkDAG(b *testing.B) {
|
||||||
// layer C
|
// layer C
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
c := fmt.Sprintf("C%d", i)
|
c := fmt.Sprintf("C%d", i)
|
||||||
g.Add(fmt.Sprintf(c))
|
g.Add(c)
|
||||||
for j := 0; j < count; j++ {
|
for j := 0; j < count; j++ {
|
||||||
// connect them to previous layers so we have something that requires reduction
|
// connect them to previous layers so we have something that requires reduction
|
||||||
g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j)))
|
g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j)))
|
||||||
|
@ -360,7 +360,7 @@ func BenchmarkDAG(b *testing.B) {
|
||||||
// layer D
|
// layer D
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
d := fmt.Sprintf("D%d", i)
|
d := fmt.Sprintf("D%d", i)
|
||||||
g.Add(fmt.Sprintf(d))
|
g.Add(d)
|
||||||
for j := 0; j < count; j++ {
|
for j := 0; j < count; j++ {
|
||||||
g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j)))
|
g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j)))
|
||||||
g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j)))
|
g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j)))
|
||||||
|
|
|
@ -337,7 +337,7 @@ func VertexName(raw Vertex) string {
|
||||||
case NamedVertex:
|
case NamedVertex:
|
||||||
return v.Name()
|
return v.Name()
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
return fmt.Sprintf("%s", v)
|
return v.String()
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("%v", v)
|
return fmt.Sprintf("%v", v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
typeOperation = "Operation"
|
|
||||||
typeTransform = "Transform"
|
|
||||||
typeWalk = "Walk"
|
|
||||||
typeDepthFirstWalk = "DepthFirstWalk"
|
|
||||||
typeReverseDepthFirstWalk = "ReverseDepthFirstWalk"
|
|
||||||
typeTransitiveReduction = "TransitiveReduction"
|
|
||||||
typeEdgeInfo = "EdgeInfo"
|
|
||||||
typeVertexInfo = "VertexInfo"
|
|
||||||
typeVisitInfo = "VisitInfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// the marshal* structs are for serialization of the graph data.
|
// the marshal* structs are for serialization of the graph data.
|
||||||
type marshalGraph struct {
|
type marshalGraph struct {
|
||||||
// Type is always "Graph", for identification as a top level object in the
|
// Type is always "Graph", for identification as a top level object in the
|
||||||
|
@ -49,36 +37,6 @@ type marshalGraph struct {
|
||||||
Cycles [][]*marshalVertex `json:",omitempty"`
|
Cycles [][]*marshalVertex `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The add, remove, connect, removeEdge methods mirror the basic Graph
|
|
||||||
// manipulations to reconstruct a marshalGraph from a debug log.
|
|
||||||
func (g *marshalGraph) add(v *marshalVertex) {
|
|
||||||
g.Vertices = append(g.Vertices, v)
|
|
||||||
sort.Sort(vertices(g.Vertices))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *marshalGraph) remove(v *marshalVertex) {
|
|
||||||
for i, existing := range g.Vertices {
|
|
||||||
if v.ID == existing.ID {
|
|
||||||
g.Vertices = append(g.Vertices[:i], g.Vertices[i+1:]...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *marshalGraph) connect(e *marshalEdge) {
|
|
||||||
g.Edges = append(g.Edges, e)
|
|
||||||
sort.Sort(edges(g.Edges))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *marshalGraph) removeEdge(e *marshalEdge) {
|
|
||||||
for i, existing := range g.Edges {
|
|
||||||
if e.Source == existing.Source && e.Target == existing.Target {
|
|
||||||
g.Edges = append(g.Edges[:i], g.Edges[i+1:]...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *marshalGraph) vertexByID(id string) *marshalVertex {
|
func (g *marshalGraph) vertexByID(id string) *marshalVertex {
|
||||||
for _, v := range g.Vertices {
|
for _, v := range g.Vertices {
|
||||||
if id == v.ID {
|
if id == v.ID {
|
||||||
|
|
|
@ -56,7 +56,6 @@ func (s Set) Intersection(other Set) Set {
|
||||||
// other doesn't.
|
// other doesn't.
|
||||||
func (s Set) Difference(other Set) Set {
|
func (s Set) Difference(other Set) Set {
|
||||||
result := make(Set)
|
result := make(Set)
|
||||||
if s != nil {
|
|
||||||
for k, v := range s {
|
for k, v := range s {
|
||||||
var ok bool
|
var ok bool
|
||||||
if other != nil {
|
if other != nil {
|
||||||
|
@ -66,7 +65,6 @@ func (s Set) Difference(other Set) Set {
|
||||||
result.Add(v)
|
result.Add(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,11 +106,6 @@ type walkerVertex struct {
|
||||||
depsCancelCh chan struct{}
|
depsCancelCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// errWalkUpstream is used in the errMap of a walk to note that an upstream
|
|
||||||
// dependency failed so this vertex wasn't run. This is not shown in the final
|
|
||||||
// user-returned error.
|
|
||||||
var errWalkUpstream = errors.New("upstream dependency failed")
|
|
||||||
|
|
||||||
// Wait waits for the completion of the walk and returns diagnostics describing
|
// Wait waits for the completion of the walk and returns diagnostics describing
|
||||||
// any problems that arose. Update should be called to populate the walk with
|
// any problems that arose. Update should be called to populate the walk with
|
||||||
// vertices and edges prior to calling this.
|
// vertices and edges prior to calling this.
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BasicNode is a digraph Node that has a name and out edges
|
|
||||||
type BasicNode struct {
|
|
||||||
Name string
|
|
||||||
NodeEdges []Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BasicNode) Edges() []Edge {
|
|
||||||
return b.NodeEdges
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BasicNode) AddEdge(edge Edge) {
|
|
||||||
b.NodeEdges = append(b.NodeEdges, edge)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BasicNode) String() string {
|
|
||||||
if b.Name == "" {
|
|
||||||
return "Node"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%v", b.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicEdge is a digraph Edge that has a name, head and tail
|
|
||||||
type BasicEdge struct {
|
|
||||||
Name string
|
|
||||||
EdgeHead *BasicNode
|
|
||||||
EdgeTail *BasicNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BasicEdge) Head() Node {
|
|
||||||
return b.EdgeHead
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tail returns the end point of the Edge
|
|
||||||
func (b *BasicEdge) Tail() Node {
|
|
||||||
return b.EdgeTail
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BasicEdge) String() string {
|
|
||||||
if b.Name == "" {
|
|
||||||
return "Edge"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%v", b.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBasic is used to parse a string in the format of:
|
|
||||||
// a -> b ; edge name
|
|
||||||
// b -> c
|
|
||||||
// Into a series of basic node and basic edges
|
|
||||||
func ParseBasic(s string) map[string]*BasicNode {
|
|
||||||
lines := strings.Split(s, "\n")
|
|
||||||
nodes := make(map[string]*BasicNode)
|
|
||||||
for _, line := range lines {
|
|
||||||
var edgeName string
|
|
||||||
if idx := strings.Index(line, ";"); idx >= 0 {
|
|
||||||
edgeName = strings.Trim(line[idx+1:], " \t\r\n")
|
|
||||||
line = line[:idx]
|
|
||||||
}
|
|
||||||
parts := strings.SplitN(line, "->", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
head_name := strings.Trim(parts[0], " \t\r\n")
|
|
||||||
tail_name := strings.Trim(parts[1], " \t\r\n")
|
|
||||||
head := nodes[head_name]
|
|
||||||
if head == nil {
|
|
||||||
head = &BasicNode{Name: head_name}
|
|
||||||
nodes[head_name] = head
|
|
||||||
}
|
|
||||||
tail := nodes[tail_name]
|
|
||||||
if tail == nil {
|
|
||||||
tail = &BasicNode{Name: tail_name}
|
|
||||||
nodes[tail_name] = tail
|
|
||||||
}
|
|
||||||
edge := &BasicEdge{
|
|
||||||
Name: edgeName,
|
|
||||||
EdgeHead: head,
|
|
||||||
EdgeTail: tail,
|
|
||||||
}
|
|
||||||
head.AddEdge(edge)
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseBasic(t *testing.T) {
|
|
||||||
spec := `a -> b ; first
|
|
||||||
b -> c ; second
|
|
||||||
b -> d ; third
|
|
||||||
z -> a`
|
|
||||||
nodes := ParseBasic(spec)
|
|
||||||
if len(nodes) != 5 {
|
|
||||||
t.Fatalf("bad: %v", nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
a := nodes["a"]
|
|
||||||
if a.Name != "a" {
|
|
||||||
t.Fatalf("bad: %v", a)
|
|
||||||
}
|
|
||||||
aEdges := a.Edges()
|
|
||||||
if len(aEdges) != 1 {
|
|
||||||
t.Fatalf("bad: %v", a.Edges())
|
|
||||||
}
|
|
||||||
if fmt.Sprintf("%v", aEdges[0]) != "first" {
|
|
||||||
t.Fatalf("bad: %v", aEdges[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
b := nodes["b"]
|
|
||||||
if len(b.Edges()) != 2 {
|
|
||||||
t.Fatalf("bad: %v", b.Edges())
|
|
||||||
}
|
|
||||||
|
|
||||||
c := nodes["c"]
|
|
||||||
if len(c.Edges()) != 0 {
|
|
||||||
t.Fatalf("bad: %v", c.Edges())
|
|
||||||
}
|
|
||||||
|
|
||||||
d := nodes["d"]
|
|
||||||
if len(d.Edges()) != 0 {
|
|
||||||
t.Fatalf("bad: %v", d.Edges())
|
|
||||||
}
|
|
||||||
|
|
||||||
z := nodes["z"]
|
|
||||||
zEdges := z.Edges()
|
|
||||||
if len(zEdges) != 1 {
|
|
||||||
t.Fatalf("bad: %v", z.Edges())
|
|
||||||
}
|
|
||||||
if fmt.Sprintf("%v", zEdges[0]) != "Edge" {
|
|
||||||
t.Fatalf("bad: %v", zEdges[0])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
// Digraph is used to represent a Directed Graph. This means
|
|
||||||
// we have a set of nodes, and a set of edges which are directed
|
|
||||||
// from a source and towards a destination
|
|
||||||
type Digraph interface {
|
|
||||||
// Nodes provides all the nodes in the graph
|
|
||||||
Nodes() []Node
|
|
||||||
|
|
||||||
// Sources provides all the source nodes in the graph
|
|
||||||
Sources() []Node
|
|
||||||
|
|
||||||
// Sinks provides all the sink nodes in the graph
|
|
||||||
Sinks() []Node
|
|
||||||
|
|
||||||
// Transpose reverses the edge directions and returns
|
|
||||||
// a new Digraph
|
|
||||||
Transpose() Digraph
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node represents a vertex in a Digraph
|
|
||||||
type Node interface {
|
|
||||||
// Edges returns the out edges for a given nod
|
|
||||||
Edges() []Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edge represents a directed edge in a Digraph
|
|
||||||
type Edge interface {
|
|
||||||
// Head returns the start point of the Edge
|
|
||||||
Head() Node
|
|
||||||
|
|
||||||
// Tail returns the end point of the Edge
|
|
||||||
Tail() Node
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WriteDot is used to emit a GraphViz compatible definition
|
|
||||||
// for a directed graph. It can be used to dump a .dot file.
|
|
||||||
func WriteDot(w io.Writer, nodes []Node) error {
|
|
||||||
w.Write([]byte("digraph {\n"))
|
|
||||||
defer w.Write([]byte("}\n"))
|
|
||||||
|
|
||||||
for _, n := range nodes {
|
|
||||||
nodeLine := fmt.Sprintf("\t\"%s\";\n", n)
|
|
||||||
|
|
||||||
w.Write([]byte(nodeLine))
|
|
||||||
|
|
||||||
for _, edge := range n.Edges() {
|
|
||||||
target := edge.Tail()
|
|
||||||
line := fmt.Sprintf("\t\"%s\" -> \"%s\" [label=\"%s\"];\n",
|
|
||||||
n, target, edge)
|
|
||||||
w.Write([]byte(line))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWriteDot(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b ; foo
|
|
||||||
a -> c
|
|
||||||
b -> d
|
|
||||||
b -> e
|
|
||||||
`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
if err := WriteDot(buf, nlist); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(string(buf.Bytes()))
|
|
||||||
expected := strings.TrimSpace(writeDotStr)
|
|
||||||
|
|
||||||
actualLines := strings.Split(actual, "\n")
|
|
||||||
expectedLines := strings.Split(expected, "\n")
|
|
||||||
|
|
||||||
if actualLines[0] != expectedLines[0] ||
|
|
||||||
actualLines[len(actualLines)-1] != expectedLines[len(expectedLines)-1] ||
|
|
||||||
len(actualLines) != len(expectedLines) {
|
|
||||||
t.Fatalf("bad: %s", actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
count := 0
|
|
||||||
for _, el := range expectedLines[1 : len(expectedLines)-1] {
|
|
||||||
for _, al := range actualLines[1 : len(actualLines)-1] {
|
|
||||||
if el == al {
|
|
||||||
count++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if count != len(expectedLines)-2 {
|
|
||||||
t.Fatalf("bad: %s", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeDotStr = `
|
|
||||||
digraph {
|
|
||||||
"a";
|
|
||||||
"a" -> "b" [label="foo"];
|
|
||||||
"a" -> "c" [label="Edge"];
|
|
||||||
"b";
|
|
||||||
"b" -> "d" [label="Edge"];
|
|
||||||
"b" -> "e" [label="Edge"];
|
|
||||||
"c";
|
|
||||||
"d";
|
|
||||||
"e";
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,111 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
// sccAcct is used ot pass around accounting information for
|
|
||||||
// the StronglyConnectedComponents algorithm
|
|
||||||
type sccAcct struct {
|
|
||||||
ExcludeSingle bool
|
|
||||||
NextIndex int
|
|
||||||
NodeIndex map[Node]int
|
|
||||||
Stack []Node
|
|
||||||
SCC [][]Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// visit assigns an index and pushes a node onto the stack
|
|
||||||
func (s *sccAcct) visit(n Node) int {
|
|
||||||
idx := s.NextIndex
|
|
||||||
s.NodeIndex[n] = idx
|
|
||||||
s.NextIndex++
|
|
||||||
s.push(n)
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
|
|
||||||
// push adds a node to the stack
|
|
||||||
func (s *sccAcct) push(n Node) {
|
|
||||||
s.Stack = append(s.Stack, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pop removes a node from the stack
|
|
||||||
func (s *sccAcct) pop() Node {
|
|
||||||
n := len(s.Stack)
|
|
||||||
if n == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
node := s.Stack[n-1]
|
|
||||||
s.Stack = s.Stack[:n-1]
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
// inStack checks if a node is in the stack
|
|
||||||
func (s *sccAcct) inStack(needle Node) bool {
|
|
||||||
for _, n := range s.Stack {
|
|
||||||
if n == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// StronglyConnectedComponents implements Tarjan's algorithm to
|
|
||||||
// find all the strongly connected components in a graph. This can
|
|
||||||
// be used to detected any cycles in a graph, as well as which nodes
|
|
||||||
// partipate in those cycles. excludeSingle is used to exclude strongly
|
|
||||||
// connected components of size one.
|
|
||||||
func StronglyConnectedComponents(nodes []Node, excludeSingle bool) [][]Node {
|
|
||||||
acct := sccAcct{
|
|
||||||
ExcludeSingle: excludeSingle,
|
|
||||||
NextIndex: 1,
|
|
||||||
NodeIndex: make(map[Node]int, len(nodes)),
|
|
||||||
}
|
|
||||||
for _, node := range nodes {
|
|
||||||
// Recurse on any non-visited nodes
|
|
||||||
if acct.NodeIndex[node] == 0 {
|
|
||||||
stronglyConnected(&acct, node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acct.SCC
|
|
||||||
}
|
|
||||||
|
|
||||||
func stronglyConnected(acct *sccAcct, node Node) int {
|
|
||||||
// Initial node visit
|
|
||||||
index := acct.visit(node)
|
|
||||||
minIdx := index
|
|
||||||
|
|
||||||
for _, edge := range node.Edges() {
|
|
||||||
target := edge.Tail()
|
|
||||||
targetIdx := acct.NodeIndex[target]
|
|
||||||
|
|
||||||
// Recurse on successor if not yet visited
|
|
||||||
if targetIdx == 0 {
|
|
||||||
minIdx = min(minIdx, stronglyConnected(acct, target))
|
|
||||||
|
|
||||||
} else if acct.inStack(target) {
|
|
||||||
// Check if the node is in the stack
|
|
||||||
minIdx = min(minIdx, targetIdx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop the strongly connected components off the stack if
|
|
||||||
// this is a root node
|
|
||||||
if index == minIdx {
|
|
||||||
var scc []Node
|
|
||||||
for {
|
|
||||||
n := acct.pop()
|
|
||||||
scc = append(scc, n)
|
|
||||||
if n == node {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !(acct.ExcludeSingle && len(scc) == 1) {
|
|
||||||
acct.SCC = append(acct.SCC, scc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minIdx
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a <= b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStronglyConnectedComponents(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
b -> c
|
|
||||||
c -> b
|
|
||||||
c -> d
|
|
||||||
d -> e`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
sccs := StronglyConnectedComponents(nlist, false)
|
|
||||||
if len(sccs) != 4 {
|
|
||||||
t.Fatalf("bad: %v", sccs)
|
|
||||||
}
|
|
||||||
|
|
||||||
sccs = StronglyConnectedComponents(nlist, true)
|
|
||||||
if len(sccs) != 1 {
|
|
||||||
t.Fatalf("bad: %v", sccs)
|
|
||||||
}
|
|
||||||
|
|
||||||
cycle := sccs[0]
|
|
||||||
if len(cycle) != 2 {
|
|
||||||
t.Fatalf("bad: %v", sccs)
|
|
||||||
}
|
|
||||||
|
|
||||||
cycleNodes := make([]string, len(cycle))
|
|
||||||
for i, c := range cycle {
|
|
||||||
cycleNodes[i] = c.(*BasicNode).Name
|
|
||||||
}
|
|
||||||
sort.Strings(cycleNodes)
|
|
||||||
|
|
||||||
expected := []string{"b", "c"}
|
|
||||||
if !reflect.DeepEqual(cycleNodes, expected) {
|
|
||||||
t.Fatalf("bad: %#v", cycleNodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStronglyConnectedComponents2(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
b -> d
|
|
||||||
b -> e
|
|
||||||
c -> f
|
|
||||||
c -> g
|
|
||||||
g -> a
|
|
||||||
`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
sccs := StronglyConnectedComponents(nlist, true)
|
|
||||||
if len(sccs) != 1 {
|
|
||||||
t.Fatalf("bad: %v", sccs)
|
|
||||||
}
|
|
||||||
|
|
||||||
cycle := sccs[0]
|
|
||||||
if len(cycle) != 3 {
|
|
||||||
t.Fatalf("bad: %v", sccs)
|
|
||||||
}
|
|
||||||
|
|
||||||
cycleNodes := make([]string, len(cycle))
|
|
||||||
for i, c := range cycle {
|
|
||||||
cycleNodes[i] = c.(*BasicNode).Name
|
|
||||||
}
|
|
||||||
sort.Strings(cycleNodes)
|
|
||||||
|
|
||||||
expected := []string{"a", "c", "g"}
|
|
||||||
if !reflect.DeepEqual(cycleNodes, expected) {
|
|
||||||
t.Fatalf("bad: %#v", cycleNodes)
|
|
||||||
}
|
|
||||||
}
|
|
113
digraph/util.go
113
digraph/util.go
|
@ -1,113 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,233 +0,0 @@
|
||||||
package digraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDepthFirstWalk(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
d -> f
|
|
||||||
e -> a ; cycle`)
|
|
||||||
root := nodes["a"]
|
|
||||||
expected := []string{
|
|
||||||
"a",
|
|
||||||
"b",
|
|
||||||
"e",
|
|
||||||
"c",
|
|
||||||
"d",
|
|
||||||
"f",
|
|
||||||
}
|
|
||||||
index := 0
|
|
||||||
DepthFirstWalk(root, func(n Node) bool {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
if expected[index] != name {
|
|
||||||
t.Fatalf("expected: %v, got %v", expected[index], name)
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInDegree(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
c -> e
|
|
||||||
d -> f`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := map[string]int{
|
|
||||||
"a": 0,
|
|
||||||
"b": 1,
|
|
||||||
"c": 1,
|
|
||||||
"d": 1,
|
|
||||||
"e": 2,
|
|
||||||
"f": 1,
|
|
||||||
}
|
|
||||||
indegree := InDegree(nlist)
|
|
||||||
for n, d := range indegree {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
exp := expected[name]
|
|
||||||
if exp != d {
|
|
||||||
t.Fatalf("Expected %d for %s, got %d",
|
|
||||||
exp, name, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutDegree(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
c -> e
|
|
||||||
d -> f`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := map[string]int{
|
|
||||||
"a": 3,
|
|
||||||
"b": 1,
|
|
||||||
"c": 1,
|
|
||||||
"d": 1,
|
|
||||||
"e": 0,
|
|
||||||
"f": 0,
|
|
||||||
}
|
|
||||||
outDegree := OutDegree(nlist)
|
|
||||||
for n, d := range outDegree {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
exp := expected[name]
|
|
||||||
if exp != d {
|
|
||||||
t.Fatalf("Expected %d for %s, got %d",
|
|
||||||
exp, name, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSinks(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
c -> e
|
|
||||||
d -> f`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
sinks := Sinks(nlist)
|
|
||||||
|
|
||||||
var haveE, haveF bool
|
|
||||||
for _, n := range sinks {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
switch name {
|
|
||||||
case "e":
|
|
||||||
haveE = true
|
|
||||||
case "f":
|
|
||||||
haveF = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !haveE || !haveF {
|
|
||||||
t.Fatalf("missing sink")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSources(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
c -> e
|
|
||||||
d -> f
|
|
||||||
x -> y`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
sources := Sources(nlist)
|
|
||||||
if len(sources) != 2 {
|
|
||||||
t.Fatalf("bad: %v", sources)
|
|
||||||
}
|
|
||||||
|
|
||||||
var haveA, haveX bool
|
|
||||||
for _, n := range sources {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
switch name {
|
|
||||||
case "a":
|
|
||||||
haveA = true
|
|
||||||
case "x":
|
|
||||||
haveX = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !haveA || !haveX {
|
|
||||||
t.Fatalf("missing source %v %v", haveA, haveX)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnreachable(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
c -> e
|
|
||||||
d -> f
|
|
||||||
f -> a
|
|
||||||
x -> y
|
|
||||||
y -> z`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
unreached := Unreachable(nodes["a"], nlist)
|
|
||||||
if len(unreached) != 3 {
|
|
||||||
t.Fatalf("bad: %v", unreached)
|
|
||||||
}
|
|
||||||
|
|
||||||
var haveX, haveY, haveZ bool
|
|
||||||
for _, n := range unreached {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
switch name {
|
|
||||||
case "x":
|
|
||||||
haveX = true
|
|
||||||
case "y":
|
|
||||||
haveY = true
|
|
||||||
case "z":
|
|
||||||
haveZ = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !haveX || !haveY || !haveZ {
|
|
||||||
t.Fatalf("missing %v %v %v", haveX, haveY, haveZ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnreachable2(t *testing.T) {
|
|
||||||
nodes := ParseBasic(`a -> b
|
|
||||||
a -> c
|
|
||||||
a -> d
|
|
||||||
b -> e
|
|
||||||
c -> e
|
|
||||||
d -> f
|
|
||||||
f -> a
|
|
||||||
x -> y
|
|
||||||
y -> z`)
|
|
||||||
var nlist []Node
|
|
||||||
for _, n := range nodes {
|
|
||||||
nlist = append(nlist, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
unreached := Unreachable(nodes["x"], nlist)
|
|
||||||
if len(unreached) != 6 {
|
|
||||||
t.Fatalf("bad: %v", unreached)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := map[string]struct{}{
|
|
||||||
"a": struct{}{},
|
|
||||||
"b": struct{}{},
|
|
||||||
"c": struct{}{},
|
|
||||||
"d": struct{}{},
|
|
||||||
"e": struct{}{},
|
|
||||||
"f": struct{}{},
|
|
||||||
}
|
|
||||||
out := map[string]struct{}{}
|
|
||||||
for _, n := range unreached {
|
|
||||||
name := n.(*BasicNode).Name
|
|
||||||
out[name] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(out, expected) {
|
|
||||||
t.Fatalf("bad: %v %v", out, expected)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue