From d8f8c1853b09d9637c2966a259fd2176de45aa59 Mon Sep 17 00:00:00 2001 From: kortschak Date: Thu, 3 Sep 2015 12:36:31 +0930 Subject: [PATCH] graph: split mutation API into smaller interfaces graph.Mutable was previously used primarily to add nodes and edges, so it is not sensible to require that edge and node deletion also be implemented - there are add-only graph implementations that gain possible optimisations by this change. This change also potentially paves the way for multigraph addition with an EdgeAdder interface. Also drop the fictional distinction between the two Copy functions. --- graph.go | 65 ++++++++++++++-------------- path/dynamic/dstarlite.go | 2 +- path/internal/testgraphs/shortest.go | 50 ++++++++++----------- path/spanning_tree.go | 4 +- path/spanning_tree_test.go | 8 ++-- simple/undirected_test.go | 2 +- 6 files changed, 66 insertions(+), 65 deletions(-) diff --git a/graph.go b/graph.go index 4dcff50b..c9160060 100644 --- a/graph.go +++ b/graph.go @@ -74,69 +74,70 @@ type Weighter interface { Weight(x, y Node) (w float64, ok bool) } -// Mutable is an interface for generalized graph mutation. -type Mutable interface { +// NodeAdder is an interface for adding arbitrary nodes to a graph. +type NodeAdder interface { // NewNodeID returns a new unique arbitrary ID. NewNodeID() int // Adds a node to the graph. AddNode panics if // the added node ID matches an existing node ID. AddNode(Node) +} +// NodeRemover is an interface for removing nodes from a graph. +type NodeRemover interface { // RemoveNode removes a node from the graph, as // well as any edges attached to it. If the node // is not in the graph it is a no-op. RemoveNode(Node) +} +// EdgeSetter is an interface for adding edges to a graph. +type EdgeSetter interface { // SetEdge adds an edge from one node to another. // If the nodes do not exist, they are added. // SetEdge will panic if the IDs of the e.From // and e.To are equal. SetEdge(Edge) +} +// EdgeRemover is an interface for removing nodes from a graph. +type EdgeRemover interface { // RemoveEdge removes the given edge, leaving the // terminal nodes. If the edge does not exist it // is a no-op. RemoveEdge(Edge) } -// MutableUndirected is an undirected graph that can be arbitrarily altered. -type MutableUndirected interface { +// Builder is a graph that can have nodes and edges added. +type Builder interface { + NodeAdder + EdgeSetter +} + +// UndirectedBuilder is an undirected graph builder. +type UndirectedBuilder interface { Undirected - Mutable + Builder } -// MutableDirected is a directed graph that can be arbitrarily altered. -type MutableDirected interface { +// DirectedBuilder is a directed graph builder. +type DirectedBuilder interface { Directed - Mutable + Builder } -// CopyUndirected copies nodes and edges as undirected edges from the source to the -// destination without first clearing the destination. CopyUndirected will panic if -// a node ID in the source graph matches a node ID in the destination. +// Copy copies nodes and edges as undirected edges from the source to the destination +// without first clearing the destination. Copy will panic if a node ID in the source +// graph matches a node ID in the destination. // -// Note that if the source is a directed graph and a fundamental cycle exists with -// two nodes where the edge weights differ, the resulting destination graph's edge -// weight between those nodes is undefined. -func CopyUndirected(dst MutableUndirected, src Graph) { - nodes := src.Nodes() - for _, n := range nodes { - dst.AddNode(n) - } - for _, u := range nodes { - for _, v := range src.From(u) { - dst.SetEdge(src.Edge(u, v)) - } - } -} - -// CopyDirected copies nodes and edges as directed edges from the source to the -// destination without first clearing the destination. CopyDirected will panic if -// a node ID in the source graph matches a node ID in the destination. If the -// source is undirected both directions will be present in the destination after -// the copy is complete. -func CopyDirected(dst MutableDirected, src Graph) { +// If the source is undirected and the destination is directed both directions will +// be present in the destination after the copy is complete. +// +// If the source is a directed graph, the destination is undirected, and a fundamental +// cycle exists with two nodes where the edge weights differ, the resulting destination +// graph's edge weight between those nodes is undefined. +func Copy(dst Builder, src Graph) { nodes := src.Nodes() for _, n := range nodes { dst.AddNode(n) diff --git a/path/dynamic/dstarlite.go b/path/dynamic/dstarlite.go index a15ab443..c7d7a9cc 100644 --- a/path/dynamic/dstarlite.go +++ b/path/dynamic/dstarlite.go @@ -33,7 +33,7 @@ type DStarLite struct { // WorldModel is a mutable weighted directed graph that returns nodes identified // by id number. type WorldModel interface { - graph.MutableDirected + graph.DirectedBuilder graph.Weighter Node(id int) graph.Node } diff --git a/path/internal/testgraphs/shortest.go b/path/internal/testgraphs/shortest.go index 8e8c0c16..f512e382 100644 --- a/path/internal/testgraphs/shortest.go +++ b/path/internal/testgraphs/shortest.go @@ -25,7 +25,7 @@ func init() { // dynamic shortest path routine in path/dynamic: DStarLite. var ShortestPathTests = []struct { Name string - Graph func() graph.Mutable + Graph func() graph.EdgeSetter Edges []simple.Edge HasNegativeWeight bool HasNegativeCycle bool @@ -40,7 +40,7 @@ var ShortestPathTests = []struct { // Positive weighted graphs. { Name: "empty directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, Weight: math.Inf(1), @@ -49,7 +49,7 @@ var ShortestPathTests = []struct { }, { Name: "empty undirected", - Graph: func() graph.Mutable { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, Weight: math.Inf(1), @@ -58,7 +58,7 @@ var ShortestPathTests = []struct { }, { Name: "one edge directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(1), W: 1}, }, @@ -74,7 +74,7 @@ var ShortestPathTests = []struct { }, { Name: "one edge self directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(1), W: 1}, }, @@ -90,7 +90,7 @@ var ShortestPathTests = []struct { }, { Name: "one edge undirected", - Graph: func() graph.Mutable { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(1), W: 1}, }, @@ -106,7 +106,7 @@ var ShortestPathTests = []struct { }, { Name: "two paths directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(2), W: 2}, {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -125,7 +125,7 @@ var ShortestPathTests = []struct { }, { Name: "two paths undirected", - Graph: func() graph.Mutable { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(2), W: 2}, {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -144,7 +144,7 @@ var ShortestPathTests = []struct { }, { Name: "confounding paths directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->5 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -178,7 +178,7 @@ var ShortestPathTests = []struct { }, { Name: "confounding paths undirected", - Graph: func() graph.Mutable { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->5 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -212,7 +212,7 @@ var ShortestPathTests = []struct { }, { Name: "confounding paths directed 2-step", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->5 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -247,7 +247,7 @@ var ShortestPathTests = []struct { }, { Name: "confounding paths undirected 2-step", - Graph: func() graph.Mutable { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->5 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -282,7 +282,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight cycle directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->4 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -306,7 +306,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight cycle^2 directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->4 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -333,7 +333,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight cycle^2 confounding directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->4 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -363,7 +363,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight cycle^3 directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->4 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -393,7 +393,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight 3·cycle^2 confounding directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->4 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -429,7 +429,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight reversed 3·cycle^2 confounding directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ // Add a path from 0->4 of weight 4 {F: simple.Node(0), T: simple.Node(1), W: 1}, @@ -465,7 +465,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight |V|·cycle^(n/|V|) directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: func() []simple.Edge { e := []simple.Edge{ // Add a path from 0->4 of weight 4 @@ -498,7 +498,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight n·cycle directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: func() []simple.Edge { e := []simple.Edge{ // Add a path from 0->4 of weight 4 @@ -531,7 +531,7 @@ var ShortestPathTests = []struct { }, { Name: "zero-weight bi-directional tree with single exit directed", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: func() []simple.Edge { e := []simple.Edge{ // Add a path from 0->4 of weight 4 @@ -579,7 +579,7 @@ var ShortestPathTests = []struct { // Negative weighted graphs. { Name: "one edge directed negative", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(1), W: -1}, }, @@ -596,7 +596,7 @@ var ShortestPathTests = []struct { }, { Name: "one edge undirected negative", - Graph: func() graph.Mutable { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node(0), T: simple.Node(1), W: -1}, }, @@ -607,7 +607,7 @@ var ShortestPathTests = []struct { }, { Name: "wp graph negative", // http://en.wikipedia.org/w/index.php?title=Johnson%27s_algorithm&oldid=564595231 - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node('w'), T: simple.Node('z'), W: 2}, {F: simple.Node('x'), T: simple.Node('w'), W: 6}, @@ -630,7 +630,7 @@ var ShortestPathTests = []struct { }, { Name: "roughgarden negative", - Graph: func() graph.Mutable { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, Edges: []simple.Edge{ {F: simple.Node('a'), T: simple.Node('b'), W: -2}, {F: simple.Node('b'), T: simple.Node('c'), W: -1}, diff --git a/path/spanning_tree.go b/path/spanning_tree.go index 2f392363..24464307 100644 --- a/path/spanning_tree.go +++ b/path/spanning_tree.go @@ -25,7 +25,7 @@ type UndirectedWeighter interface { // first. The weight of the minimum spanning tree is returned. If g is not connected, // a minimum spanning forest will be constructed in dst and the sum of minimum // spanning tree weights will be returned. -func Prim(dst graph.MutableUndirected, g UndirectedWeighter) float64 { +func Prim(dst graph.UndirectedBuilder, g UndirectedWeighter) float64 { nodes := g.Nodes() if len(nodes) == 0 { return 0 @@ -145,7 +145,7 @@ type UndirectedWeightLister interface { // first. The weight of the minimum spanning tree is returned. If g is not connected, // a minimum spanning forest will be constructed in dst and the sum of minimum // spanning tree weights will be returned. -func Kruskal(dst graph.MutableUndirected, g UndirectedWeightLister) float64 { +func Kruskal(dst graph.UndirectedBuilder, g UndirectedWeightLister) float64 { edges := g.Edges() ascend := make([]simple.Edge, 0, len(edges)) for _, e := range edges { diff --git a/path/spanning_tree_test.go b/path/spanning_tree_test.go index d4742d4c..49f4fe3b 100644 --- a/path/spanning_tree_test.go +++ b/path/spanning_tree_test.go @@ -26,7 +26,7 @@ func init() { } type spanningGraph interface { - graph.MutableUndirected + graph.UndirectedBuilder graph.Weighter Edges() []graph.Edge } @@ -240,7 +240,7 @@ var spanningTreeTests = []struct { }, } -func testMinumumSpanning(mst func(dst graph.MutableUndirected, g spanningGraph) float64, t *testing.T) { +func testMinumumSpanning(mst func(dst graph.UndirectedBuilder, g spanningGraph) float64, t *testing.T) { for _, test := range spanningTreeTests { g := test.graph() for _, e := range test.edges { @@ -282,13 +282,13 @@ func testMinumumSpanning(mst func(dst graph.MutableUndirected, g spanningGraph) } func TestKruskal(t *testing.T) { - testMinumumSpanning(func(dst graph.MutableUndirected, g spanningGraph) float64 { + testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 { return Kruskal(dst, g) }, t) } func TestPrim(t *testing.T) { - testMinumumSpanning(func(dst graph.MutableUndirected, g spanningGraph) float64 { + testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 { return Prim(dst, g) }, t) } diff --git a/simple/undirected_test.go b/simple/undirected_test.go index bcb01aad..e272c72f 100644 --- a/simple/undirected_test.go +++ b/simple/undirected_test.go @@ -14,7 +14,7 @@ import ( var _ graph.Graph = (*UndirectedGraph)(nil) func TestAssertMutableNotDirected(t *testing.T) { - var g graph.MutableUndirected = NewUndirectedGraph(0, math.Inf(1)) + var g graph.UndirectedBuilder = NewUndirectedGraph(0, math.Inf(1)) if _, ok := g.(graph.Directed); ok { t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!") }