dag: new Graph API

This commit is contained in:
Mitchell Hashimoto 2015-01-23 15:01:58 -08:00
parent 87f4c3aae1
commit 2a910585a2
4 changed files with 219 additions and 24 deletions

27
dag/edge.go Normal file
View File

@ -0,0 +1,27 @@
package dag
// Edge represents an edge in the graph, with a source and target vertex.
type Edge interface {
Source() Vertex
Target() Vertex
}
// BasicEdge returns an Edge implementation that simply tracks the source
// and target given as-is.
func BasicEdge(source, target Vertex) Edge {
return &basicEdge{S: source, T: target}
}
// basicEdge is a basic implementation of Edge that has the source and
// target vertex.
type basicEdge struct {
S, T Vertex
}
func (e *basicEdge) Source() Vertex {
return e.S
}
func (e *basicEdge) Target() Vertex {
return e.T
}

View File

@ -4,50 +4,106 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"sort" "sort"
"sync"
) )
// Graph is used to represent a dependency graph. // Graph is used to represent a dependency graph.
type Graph struct { type Graph struct {
Nodes []Node vertices []Vertex
edges []Edge
downEdges map[Vertex]*set
upEdges map[Vertex]*set
once sync.Once
} }
// Node is an element of the graph that has other dependencies. // Vertex of the graph.
type Node interface { type Vertex interface{}
Deps() []Node
}
// NamedNode is an optional interface implementation of a Node that // NamedVertex is an optional interface that can be implemented by Vertex
// can have a name. If this is implemented, this will be used for various // to give it a human-friendly name that is used for outputting the graph.
// output. type NamedVertex interface {
type NamedNode interface { Vertex
Node
Name() string Name() string
} }
// Vertices returns the list of all the vertices in the graph.
func (g *Graph) Vertices() []Vertex {
return g.vertices
}
// Edges returns the list of all the edges in the graph.
func (g *Graph) Edges() []Edge {
return g.edges
}
// Add adds a vertex to the graph. This is safe to call multiple time with
// the same Vertex.
func (g *Graph) Add(v Vertex) {
g.once.Do(g.init)
g.vertices = append(g.vertices, v)
}
// Connect adds an edge with the given source and target. This is safe to
// call multiple times with the same value. Note that the same value is
// verified through pointer equality of the vertices, not through the
// value of the edge itself.
func (g *Graph) Connect(edge Edge) {
g.once.Do(g.init)
source := edge.Source()
target := edge.Target()
// Do we have this already? If so, don't add it again.
if s, ok := g.downEdges[source]; ok && s.Include(target) {
return
}
// TODO: add all edges
g.edges = append(g.edges, edge)
// Add the down edge
s, ok := g.downEdges[source]
if !ok {
s = new(set)
g.downEdges[source] = s
}
s.Add(target)
// Add the up edge
s, ok = g.upEdges[target]
if !ok {
s = new(set)
g.upEdges[target] = s
}
s.Add(source)
}
// String outputs some human-friendly output for the graph structure.
func (g *Graph) String() string { func (g *Graph) String() string {
var buf bytes.Buffer var buf bytes.Buffer
// Build the list of node names and a mapping so that we can more // Build the list of node names and a mapping so that we can more
// easily alphabetize the output to remain deterministic. // easily alphabetize the output to remain deterministic.
names := make([]string, 0, len(g.Nodes)) names := make([]string, 0, len(g.vertices))
mapping := make(map[string]Node, len(g.Nodes)) mapping := make(map[string]Vertex, len(g.vertices))
for _, n := range g.Nodes { for _, v := range g.vertices {
name := nodeName(n) name := vertName(v)
names = append(names, name) names = append(names, name)
mapping[name] = n mapping[name] = v
} }
sort.Strings(names) sort.Strings(names)
// Write each node in order... // Write each node in order...
for _, name := range names { for _, name := range names {
n := mapping[name] v := mapping[name]
targets := g.downEdges[v]
buf.WriteString(fmt.Sprintf("%s\n", name)) buf.WriteString(fmt.Sprintf("%s\n", name))
// Alphabetize dependencies // Alphabetize dependencies
depsRaw := n.Deps() deps := make([]string, 0, targets.Len())
deps := make([]string, 0, len(depsRaw)) for _, target := range targets.List() {
for _, d := range depsRaw { deps = append(deps, vertName(target))
deps = append(deps, nodeName(d))
} }
sort.Strings(deps) sort.Strings(deps)
@ -60,11 +116,20 @@ func (g *Graph) String() string {
return buf.String() return buf.String()
} }
func nodeName(n Node) string { func (g *Graph) init() {
switch v := n.(type) { g.vertices = make([]Vertex, 0, 5)
case NamedNode: g.edges = make([]Edge, 0, 2)
g.downEdges = make(map[Vertex]*set)
g.upEdges = make(map[Vertex]*set)
}
func vertName(raw Vertex) string {
switch v := raw.(type) {
case NamedVertex:
return v.Name() return v.Name()
default: case fmt.Stringer:
return fmt.Sprintf("%s", v) return fmt.Sprintf("%s", v)
default:
return fmt.Sprintf("%v", v)
} }
} }

45
dag/graph_test.go Normal file
View File

@ -0,0 +1,45 @@
package dag
import (
"strings"
"testing"
)
func TestGraph_empty(t *testing.T) {
var g Graph
g.Add(1)
g.Add(2)
g.Add(3)
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testGraphEmptyStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
func TestGraph_basic(t *testing.T) {
var g Graph
g.Add(1)
g.Add(2)
g.Add(3)
g.Connect(BasicEdge(1, 3))
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testGraphBasicStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
const testGraphBasicStr = `
1
3
2
3
`
const testGraphEmptyStr = `
1
2
3
`

58
dag/set.go Normal file
View File

@ -0,0 +1,58 @@
package dag
import (
"sync"
)
// set is an internal Set data structure that is based on simply using
// pointers as the hash key into a map.
type set struct {
m map[interface{}]struct{}
once sync.Once
}
// Add adds an item to the set
func (s *set) Add(v interface{}) {
s.once.Do(s.init)
s.m[v] = struct{}{}
}
// Delete removes an item from the set.
func (s *set) Delete(v interface{}) {
s.once.Do(s.init)
delete(s.m, v)
}
// Include returns true/false of whether a value is in the set.
func (s *set) Include(v interface{}) bool {
s.once.Do(s.init)
_, ok := s.m[v]
return ok
}
// Len is the number of items in the set.
func (s *set) Len() int {
if s == nil {
return 0
}
return len(s.m)
}
// List returns the list of set elements.
func (s *set) List() []interface{} {
if s == nil {
return nil
}
r := make([]interface{}, 0, len(s.m))
for k, _ := range s.m {
r = append(r, k)
}
return r
}
func (s *set) init() {
s.m = make(map[interface{}]struct{})
}