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"
"fmt"
"sort"
"sync"
)
// Graph is used to represent a dependency graph.
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.
type Node interface {
Deps() []Node
}
// Vertex of the graph.
type Vertex interface{}
// NamedNode is an optional interface implementation of a Node that
// can have a name. If this is implemented, this will be used for various
// output.
type NamedNode interface {
Node
// NamedVertex is an optional interface that can be implemented by Vertex
// to give it a human-friendly name that is used for outputting the graph.
type NamedVertex interface {
Vertex
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 {
var buf bytes.Buffer
// Build the list of node names and a mapping so that we can more
// easily alphabetize the output to remain deterministic.
names := make([]string, 0, len(g.Nodes))
mapping := make(map[string]Node, len(g.Nodes))
for _, n := range g.Nodes {
name := nodeName(n)
names := make([]string, 0, len(g.vertices))
mapping := make(map[string]Vertex, len(g.vertices))
for _, v := range g.vertices {
name := vertName(v)
names = append(names, name)
mapping[name] = n
mapping[name] = v
}
sort.Strings(names)
// Write each node in order...
for _, name := range names {
n := mapping[name]
v := mapping[name]
targets := g.downEdges[v]
buf.WriteString(fmt.Sprintf("%s\n", name))
// Alphabetize dependencies
depsRaw := n.Deps()
deps := make([]string, 0, len(depsRaw))
for _, d := range depsRaw {
deps = append(deps, nodeName(d))
deps := make([]string, 0, targets.Len())
for _, target := range targets.List() {
deps = append(deps, vertName(target))
}
sort.Strings(deps)
@ -60,11 +116,20 @@ func (g *Graph) String() string {
return buf.String()
}
func nodeName(n Node) string {
switch v := n.(type) {
case NamedNode:
func (g *Graph) init() {
g.vertices = make([]Vertex, 0, 5)
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()
default:
case fmt.Stringer:
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{})
}