diff --git a/dag/edge.go b/dag/edge.go new file mode 100644 index 000000000..bddea2381 --- /dev/null +++ b/dag/edge.go @@ -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 +} diff --git a/dag/graph.go b/dag/graph.go index a81a2c1a3..4d4450cc4 100644 --- a/dag/graph.go +++ b/dag/graph.go @@ -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) } } diff --git a/dag/graph_test.go b/dag/graph_test.go new file mode 100644 index 000000000..c49d361fc --- /dev/null +++ b/dag/graph_test.go @@ -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 +` diff --git a/dag/set.go b/dag/set.go new file mode 100644 index 000000000..eb02bff32 --- /dev/null +++ b/dag/set.go @@ -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{}) +}