Skip to content
This repository has been archived by the owner on Apr 26, 2019. It is now read-only.

Commit

Permalink
graph: split mutation API into smaller interfaces
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kortschak committed Sep 3, 2015
1 parent 0c18460 commit d8f8c18
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 65 deletions.
65 changes: 33 additions & 32 deletions graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion path/dynamic/dstarlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
50 changes: 25 additions & 25 deletions path/internal/testgraphs/shortest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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},
},
Expand All @@ -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},
},
Expand All @@ -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},
},
Expand All @@ -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},
Expand All @@ -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},
Expand All @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand All @@ -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},
Expand All @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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},
},
Expand All @@ -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},
},
Expand All @@ -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},
Expand All @@ -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},
Expand Down
4 changes: 2 additions & 2 deletions path/spanning_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions path/spanning_tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func init() {
}

type spanningGraph interface {
graph.MutableUndirected
graph.UndirectedBuilder
graph.Weighter
Edges() []graph.Edge
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion simple/undirected_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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!")
}
Expand Down

0 comments on commit d8f8c18

Please sign in to comment.