dag: new Graph API
This commit is contained in:
parent
87f4c3aae1
commit
2a910585a2
|
@ -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
|
||||||
|
}
|
113
dag/graph.go
113
dag/graph.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
`
|
|
@ -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{})
|
||||||
|
}
|
Loading…
Reference in New Issue