From 14c7f9569f08de1c53926eaef8d8a842dfc4daa3 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Thu, 13 Dec 2018 07:56:04 +1030 Subject: [PATCH] graph: add Empty universal iterator for empty returns --- graph/doc.go | 3 + graph/graph.go | 8 ++ graph/multi/directed.go | 19 +++- graph/multi/directed_test.go | 8 +- graph/multi/doc.go | 2 + graph/multi/undirected.go | 14 ++- graph/multi/undirected_test.go | 8 +- graph/multi/weighted_directed.go | 26 +++-- graph/multi/weighted_directed_test.go | 10 +- graph/multi/weighted_undirected.go | 21 ++-- graph/multi/weighted_undirected_test.go | 10 +- graph/multigraph.go | 16 +++ graph/nodes_edges.go | 37 +++++++ graph/path/internal/testgraphs/grid.go | 5 +- graph/path/internal/testgraphs/limited.go | 5 +- graph/simple/dense_directed_matrix.go | 17 +++- graph/simple/dense_undirected_matrix.go | 12 ++- graph/simple/densegraph_test.go | 56 +++++------ graph/simple/directed.go | 15 ++- graph/simple/directed_test.go | 10 +- graph/simple/doc.go | 2 + graph/simple/undirected.go | 12 ++- graph/simple/undirected_test.go | 10 +- graph/simple/weighted_directed.go | 18 +++- graph/simple/weighted_directed_test.go | 14 +-- graph/simple/weighted_undirected.go | 15 ++- graph/simple/weighted_undirected_test.go | 14 +-- graph/testgraph/testgraph.go | 113 ++++++++++++++++------ graph/topo/johnson_cycles.go | 2 +- 29 files changed, 362 insertions(+), 140 deletions(-) diff --git a/graph/doc.go b/graph/doc.go index 0e76ac22..7eedd09c 100644 --- a/graph/doc.go +++ b/graph/doc.go @@ -3,4 +3,7 @@ // license that can be found in the LICENSE file. // Package graph defines graph interfaces. +// +// Routines to test contract compliance by user implemented graph types +// are available in gonum.org/v1/gonum/graph/testgraph. package graph // import "gonum.org/v1/gonum/graph" diff --git a/graph/graph.go b/graph/graph.go index 839f234e..891322a1 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -32,10 +32,14 @@ type Graph interface { Node(id int64) Node // Nodes returns all the nodes in the graph. + // + // Nodes must not return nil. Nodes() Nodes // From returns all nodes that can be reached directly // from the node with the given ID. + // + // From must not return nil. From(id int64) Nodes // HasEdgeBetween returns whether an edge exists between @@ -99,6 +103,8 @@ type Directed interface { // To returns all nodes that can reach directly // to the node with the given ID. + // + // To must not return nil. To(id int64) Nodes } @@ -113,6 +119,8 @@ type WeightedDirected interface { // To returns all nodes that can reach directly // to the node with the given ID. + // + // To must not return nil. To(id int64) Nodes } diff --git a/graph/multi/directed.go b/graph/multi/directed.go index d2931e7c..c63deb26 100644 --- a/graph/multi/directed.go +++ b/graph/multi/directed.go @@ -73,7 +73,7 @@ func (g *DirectedGraph) Edge(uid, vid int64) graph.Edge { // is a multi.Edge. func (g *DirectedGraph) Edges() graph.Edges { if len(g.nodes) == 0 { - return nil + return graph.Empty } var edges []graph.Edge for _, u := range g.nodes { @@ -91,13 +91,16 @@ func (g *DirectedGraph) Edges() graph.Edges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *DirectedGraph) From(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } from := make([]graph.Node, len(g.from[id])) @@ -106,6 +109,9 @@ func (g *DirectedGraph) From(id int64) graph.Nodes { from[i] = g.nodes[vid] i++ } + if len(from) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(from) } @@ -130,7 +136,7 @@ func (g *DirectedGraph) HasEdgeFromTo(uid, vid int64) bool { func (g *DirectedGraph) Lines(uid, vid int64) graph.Lines { edge := g.from[uid][vid] if len(edge) == 0 { - return nil + return graph.Empty } var lines []graph.Line for _, l := range edge { @@ -167,7 +173,7 @@ func (g *DirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *DirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 @@ -256,7 +262,7 @@ func (g *DirectedGraph) SetLine(l graph.Line) { // To returns all nodes in g that can reach directly to n. func (g *DirectedGraph) To(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } to := make([]graph.Node, len(g.to[id])) @@ -265,5 +271,8 @@ func (g *DirectedGraph) To(id int64) graph.Nodes { to[i] = g.nodes[uid] i++ } + if len(to) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(to) } diff --git a/graph/multi/directed_test.go b/graph/multi/directed_test.go index 50ec24ae..738b27c2 100644 --- a/graph/multi/directed_test.go +++ b/graph/multi/directed_test.go @@ -53,16 +53,16 @@ func TestDirected(t *testing.T) { testgraph.NodeExistence(t, directedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, directedBuilder) + testgraph.ReturnAdjacentNodes(t, directedBuilder, true) }) t.Run("ReturnAllLines", func(t *testing.T) { - testgraph.ReturnAllLines(t, directedBuilder) + testgraph.ReturnAllLines(t, directedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, directedBuilder) + testgraph.ReturnAllNodes(t, directedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, directedBuilder) + testgraph.ReturnNodeSlice(t, directedBuilder, true) }) } diff --git a/graph/multi/doc.go b/graph/multi/doc.go index 22dc2655..7a3c2675 100644 --- a/graph/multi/doc.go +++ b/graph/multi/doc.go @@ -4,4 +4,6 @@ // Package multi provides a suite of multigraph implementations satisfying // the gonum/graph interfaces. +// +// All types in multi return the graph.Empty value for empty iterators. package multi // import "gonum.org/v1/gonum/graph/multi" diff --git a/graph/multi/undirected.go b/graph/multi/undirected.go index f2df8782..c9cdfb27 100644 --- a/graph/multi/undirected.go +++ b/graph/multi/undirected.go @@ -75,7 +75,7 @@ func (g *UndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge { // is a multi.Edge. func (g *UndirectedGraph) Edges() graph.Edges { if len(g.lines) == 0 { - return nil + return graph.Empty } var edges []graph.Edge seen := make(map[int64]struct{}) @@ -99,13 +99,16 @@ func (g *UndirectedGraph) Edges() graph.Edges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *UndirectedGraph) From(id int64) graph.Nodes { if _, ok := g.nodes[id]; !ok { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.lines[id])) @@ -114,6 +117,9 @@ func (g *UndirectedGraph) From(id int64) graph.Nodes { nodes[i] = g.nodes[from] i++ } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -132,7 +138,7 @@ func (g *UndirectedGraph) Lines(uid, vid int64) graph.Lines { // LinesBetween returns the lines between nodes x and y. func (g *UndirectedGraph) LinesBetween(xid, yid int64) graph.Lines { if !g.HasEdgeBetween(xid, yid) { - return nil + return graph.Empty } var lines []graph.Line for _, l := range g.lines[xid][yid] { @@ -169,7 +175,7 @@ func (g *UndirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *UndirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 diff --git a/graph/multi/undirected_test.go b/graph/multi/undirected_test.go index 7970e998..8d4f0d3d 100644 --- a/graph/multi/undirected_test.go +++ b/graph/multi/undirected_test.go @@ -53,16 +53,16 @@ func TestUndirected(t *testing.T) { testgraph.NodeExistence(t, undirectedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, undirectedBuilder) + testgraph.ReturnAdjacentNodes(t, undirectedBuilder, true) }) t.Run("ReturnAllLines", func(t *testing.T) { - testgraph.ReturnAllLines(t, undirectedBuilder) + testgraph.ReturnAllLines(t, undirectedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, undirectedBuilder) + testgraph.ReturnAllNodes(t, undirectedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, undirectedBuilder) + testgraph.ReturnNodeSlice(t, undirectedBuilder, true) }) } diff --git a/graph/multi/weighted_directed.go b/graph/multi/weighted_directed.go index 2fbb5f09..047d847f 100644 --- a/graph/multi/weighted_directed.go +++ b/graph/multi/weighted_directed.go @@ -78,7 +78,7 @@ func (g *WeightedDirectedGraph) Edge(uid, vid int64) graph.Edge { // is a multi.WeightedEdge. func (g *WeightedDirectedGraph) Edges() graph.Edges { if len(g.nodes) == 0 { - return nil + return graph.Empty } var edges []graph.Edge for _, u := range g.nodes { @@ -97,13 +97,16 @@ func (g *WeightedDirectedGraph) Edges() graph.Edges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *WeightedDirectedGraph) From(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } from := make([]graph.Node, len(g.from[id])) @@ -112,6 +115,9 @@ func (g *WeightedDirectedGraph) From(id int64) graph.Nodes { from[i] = g.nodes[vid] i++ } + if len(from) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(from) } @@ -138,7 +144,7 @@ func (g *WeightedDirectedGraph) HasEdgeFromTo(uid, vid int64) bool { func (g *WeightedDirectedGraph) Lines(uid, vid int64) graph.Lines { edge := g.from[uid][vid] if len(edge) == 0 { - return nil + return graph.Empty } var lines []graph.Line for _, l := range edge { @@ -175,7 +181,7 @@ func (g *WeightedDirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *WeightedDirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 @@ -265,7 +271,7 @@ func (g *WeightedDirectedGraph) SetWeightedLine(l graph.WeightedLine) { // To returns all nodes in g that can reach directly to n. func (g *WeightedDirectedGraph) To(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } to := make([]graph.Node, len(g.to[id])) @@ -274,6 +280,9 @@ func (g *WeightedDirectedGraph) To(id int64) graph.Nodes { to[i] = g.nodes[uid] i++ } + if len(to) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(to) } @@ -303,7 +312,7 @@ func (g *WeightedDirectedGraph) WeightedEdge(uid, vid int64) graph.WeightedEdge // is a multi.WeightedEdge. func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges { if len(g.nodes) == 0 { - return nil + return graph.Empty } var edges []graph.WeightedEdge for _, u := range g.nodes { @@ -322,6 +331,9 @@ func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedWeightedEdges(edges) } @@ -330,7 +342,7 @@ func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges { func (g *WeightedDirectedGraph) WeightedLines(uid, vid int64) graph.WeightedLines { edge := g.from[uid][vid] if len(edge) == 0 { - return nil + return graph.Empty } var lines []graph.WeightedLine for _, l := range edge { diff --git a/graph/multi/weighted_directed_test.go b/graph/multi/weighted_directed_test.go index c1654a31..09668c36 100644 --- a/graph/multi/weighted_directed_test.go +++ b/graph/multi/weighted_directed_test.go @@ -65,19 +65,19 @@ func TestWeightedDirected(t *testing.T) { testgraph.NodeExistence(t, weightedDirectedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder) + testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder, true) }) t.Run("ReturnAllLines", func(t *testing.T) { - testgraph.ReturnAllLines(t, weightedDirectedBuilder) + testgraph.ReturnAllLines(t, weightedDirectedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, weightedDirectedBuilder) + testgraph.ReturnAllNodes(t, weightedDirectedBuilder, true) }) t.Run("ReturnAllWeightedLines", func(t *testing.T) { - testgraph.ReturnAllWeightedLines(t, weightedDirectedBuilder) + testgraph.ReturnAllWeightedLines(t, weightedDirectedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, weightedDirectedBuilder) + testgraph.ReturnNodeSlice(t, weightedDirectedBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, weightedDirectedBuilder) diff --git a/graph/multi/weighted_undirected.go b/graph/multi/weighted_undirected.go index c814869e..f8ed28ee 100644 --- a/graph/multi/weighted_undirected.go +++ b/graph/multi/weighted_undirected.go @@ -80,7 +80,7 @@ func (g *WeightedUndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge { // is a multi.Edge. func (g *WeightedUndirectedGraph) Edges() graph.Edges { if len(g.lines) == 0 { - return nil + return graph.Empty } var edges []graph.Edge seen := make(map[int64]struct{}) @@ -105,13 +105,16 @@ func (g *WeightedUndirectedGraph) Edges() graph.Edges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes { if _, ok := g.nodes[id]; !ok { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.lines[id])) @@ -120,6 +123,9 @@ func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes { nodes[i] = g.nodes[from] i++ } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -139,7 +145,7 @@ func (g *WeightedUndirectedGraph) Lines(uid, vid int64) graph.Lines { func (g *WeightedUndirectedGraph) LinesBetween(xid, yid int64) graph.Lines { edge := g.lines[xid][yid] if len(edge) == 0 { - return nil + return graph.Empty } var lines []graph.Line seen := make(map[int64]struct{}) @@ -182,7 +188,7 @@ func (g *WeightedUndirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *WeightedUndirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 @@ -291,7 +297,7 @@ func (g *WeightedUndirectedGraph) WeightedEdgeBetween(xid, yid int64) graph.Weig // is a multi.Edge. func (g *WeightedUndirectedGraph) WeightedEdges() graph.WeightedEdges { if len(g.lines) == 0 { - return nil + return graph.Empty } var edges []graph.WeightedEdge seen := make(map[int64]struct{}) @@ -316,6 +322,9 @@ func (g *WeightedUndirectedGraph) WeightedEdges() graph.WeightedEdges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedWeightedEdges(edges) } @@ -329,7 +338,7 @@ func (g *WeightedUndirectedGraph) WeightedLines(uid, vid int64) graph.WeightedLi func (g *WeightedUndirectedGraph) WeightedLinesBetween(xid, yid int64) graph.WeightedLines { edge := g.lines[xid][yid] if len(edge) == 0 { - return nil + return graph.Empty } var lines []graph.WeightedLine seen := make(map[int64]struct{}) diff --git a/graph/multi/weighted_undirected_test.go b/graph/multi/weighted_undirected_test.go index 08fe7bf4..2aea519b 100644 --- a/graph/multi/weighted_undirected_test.go +++ b/graph/multi/weighted_undirected_test.go @@ -65,19 +65,19 @@ func TestWeightedUndirected(t *testing.T) { testgraph.NodeExistence(t, weightedUndirectedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder) + testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder, true) }) t.Run("ReturnAllLines", func(t *testing.T) { - testgraph.ReturnAllLines(t, weightedUndirectedBuilder) + testgraph.ReturnAllLines(t, weightedUndirectedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, weightedUndirectedBuilder) + testgraph.ReturnAllNodes(t, weightedUndirectedBuilder, true) }) t.Run("ReturnAllWeightedLines", func(t *testing.T) { - testgraph.ReturnAllWeightedLines(t, weightedUndirectedBuilder) + testgraph.ReturnAllWeightedLines(t, weightedUndirectedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder) + testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, weightedUndirectedBuilder) diff --git a/graph/multigraph.go b/graph/multigraph.go index 9deafb88..75a9f3cc 100644 --- a/graph/multigraph.go +++ b/graph/multigraph.go @@ -24,10 +24,14 @@ type Multigraph interface { Node(id int64) Node // Nodes returns all the nodes in the multigraph. + // + // Nodes must not return nil. Nodes() Nodes // From returns all nodes that can be reached directly // from the node with the given ID. + // + // From must not return nil. From(id int64) Nodes // HasEdgeBetween returns whether an edge exists between @@ -38,6 +42,8 @@ type Multigraph interface { // vid, if any such lines exist and nil otherwise. The // node v must be directly reachable from u as defined by // the From method. + // + // Lines must not return nil. Lines(uid, vid int64) Lines } @@ -49,6 +55,8 @@ type WeightedMultigraph interface { // with IDs uid and vid if any such lines exist and nil // otherwise. The node v must be directly reachable // from u as defined by the From method. + // + // WeightedLines must not return nil. WeightedLines(uid, vid int64) WeightedLines } @@ -58,6 +66,8 @@ type UndirectedMultigraph interface { // LinesBetween returns the lines between nodes x and y // with IDs xid and yid. + // + // LinesBetween must not return nil. LinesBetween(xid, yid int64) Lines } @@ -67,6 +77,8 @@ type WeightedUndirectedMultigraph interface { // WeightedLinesBetween returns the lines between nodes // x and y with IDs xid and yid. + // + // WeightedLinesBetween must not return nil. WeightedLinesBetween(xid, yid int64) WeightedLines } @@ -81,6 +93,8 @@ type DirectedMultigraph interface { // To returns all nodes that can reach directly // to the node with the given ID. + // + // To must not return nil. To(id int64) Nodes } @@ -95,6 +109,8 @@ type WeightedDirectedMultigraph interface { // To returns all nodes that can reach directly // to the node with the given ID. + // + // To must not return nil. To(id int64) Nodes } diff --git a/graph/nodes_edges.go b/graph/nodes_edges.go index c12dbe1e..fffa4182 100644 --- a/graph/nodes_edges.go +++ b/graph/nodes_edges.go @@ -227,3 +227,40 @@ func WeightedLinesOf(it WeightedLines) []WeightedLine { } return l } + +// Empty is an empty set of nodes, edges or lines. It should be used when +// a graph returns a zero-length Iterator. Empty implements the slicer +// interfaces for nodes, edges and lines, returning nil for each of these. +const Empty = nothing + +var ( + _ Iterator = Empty + _ Nodes = Empty + _ NodeSlicer = Empty + _ Edges = Empty + _ EdgeSlicer = Empty + _ WeightedEdges = Empty + _ WeightedEdgeSlicer = Empty + _ Lines = Empty + _ LineSlicer = Empty + _ WeightedLines = Empty + _ WeightedLineSlicer = Empty +) + +const nothing = empty(true) + +type empty bool + +func (empty) Next() bool { return false } +func (empty) Len() int { return 0 } +func (empty) Reset() {} +func (empty) Node() Node { return nil } +func (empty) NodeSlice() []Node { return nil } +func (empty) Edge() Edge { return nil } +func (empty) EdgeSlice() []Edge { return nil } +func (empty) WeightedEdge() WeightedEdge { return nil } +func (empty) WeightedEdgeSlice() []WeightedEdge { return nil } +func (empty) Line() Line { return nil } +func (empty) LineSlice() []Line { return nil } +func (empty) WeightedLine() WeightedLine { return nil } +func (empty) WeightedLineSlice() []WeightedLine { return nil } diff --git a/graph/path/internal/testgraphs/grid.go b/graph/path/internal/testgraphs/grid.go index 14ac954a..5af7ed56 100644 --- a/graph/path/internal/testgraphs/grid.go +++ b/graph/path/internal/testgraphs/grid.go @@ -169,7 +169,7 @@ func (g *Grid) NodeAt(r, c int) graph.Node { // ends of an edge must be open. func (g *Grid) From(uid int64) graph.Nodes { if !g.HasOpen(uid) { - return nil + return graph.Empty } nr, nc := g.RowCol(uid) var to []graph.Node @@ -180,6 +180,9 @@ func (g *Grid) From(uid int64) graph.Nodes { } } } + if len(to) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(to) } diff --git a/graph/path/internal/testgraphs/limited.go b/graph/path/internal/testgraphs/limited.go index cbb232ec..bc086744 100644 --- a/graph/path/internal/testgraphs/limited.go +++ b/graph/path/internal/testgraphs/limited.go @@ -178,7 +178,7 @@ func (l *LimitedVisionGrid) has(id int64) bool { // From returns nodes that are optimistically reachable from u. func (l *LimitedVisionGrid) From(uid int64) graph.Nodes { if !l.has(uid) { - return nil + return graph.Empty } nr, nc := l.RowCol(uid) @@ -190,6 +190,9 @@ func (l *LimitedVisionGrid) From(uid int64) graph.Nodes { } } } + if len(to) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(to) } diff --git a/graph/simple/dense_directed_matrix.go b/graph/simple/dense_directed_matrix.go index 6d397527..3daca9ad 100644 --- a/graph/simple/dense_directed_matrix.go +++ b/graph/simple/dense_directed_matrix.go @@ -93,13 +93,16 @@ func (g *DirectedMatrix) Edges() graph.Edges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *DirectedMatrix) From(id int64) graph.Nodes { if !g.has(id) { - return nil + return graph.Empty } var nodes []graph.Node _, c := g.mat.Dims() @@ -112,6 +115,9 @@ func (g *DirectedMatrix) From(id int64) graph.Nodes { nodes = append(nodes, g.Node(int64(j))) } } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -169,6 +175,7 @@ func (g *DirectedMatrix) Nodes() graph.Nodes { return iterator.NewOrderedNodes(nodes) } r, _ := g.mat.Dims() + // Matrix graphs must have at least one node. return iterator.NewImplicitNodes(0, r, newSimpleNode) } @@ -224,7 +231,7 @@ func (g *DirectedMatrix) setWeightedEdge(e graph.Edge, weight float64) { // To returns all nodes in g that can reach directly to n. func (g *DirectedMatrix) To(id int64) graph.Nodes { if !g.has(id) { - return nil + return graph.Empty } var nodes []graph.Node r, _ := g.mat.Dims() @@ -237,6 +244,9 @@ func (g *DirectedMatrix) To(id int64) graph.Nodes { nodes = append(nodes, g.Node(int64(i))) } } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -279,6 +289,9 @@ func (g *DirectedMatrix) WeightedEdges() graph.WeightedEdges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedWeightedEdges(edges) } diff --git a/graph/simple/dense_undirected_matrix.go b/graph/simple/dense_undirected_matrix.go index 06bda986..f51debb4 100644 --- a/graph/simple/dense_undirected_matrix.go +++ b/graph/simple/dense_undirected_matrix.go @@ -95,13 +95,16 @@ func (g *UndirectedMatrix) Edges() graph.Edges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *UndirectedMatrix) From(id int64) graph.Nodes { if !g.has(id) { - return nil + return graph.Empty } var nodes []graph.Node r := g.mat.Symmetric() @@ -114,6 +117,9 @@ func (g *UndirectedMatrix) From(id int64) graph.Nodes { nodes = append(nodes, g.Node(int64(i))) } } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -156,6 +162,7 @@ func (g *UndirectedMatrix) Nodes() graph.Nodes { return iterator.NewOrderedNodes(nodes) } r := g.mat.Symmetric() + // Matrix graphs must have at least one node. return iterator.NewImplicitNodes(0, r, newSimpleNode) } @@ -249,6 +256,9 @@ func (g *UndirectedMatrix) WeightedEdges() graph.WeightedEdges { } } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedWeightedEdges(edges) } diff --git a/graph/simple/densegraph_test.go b/graph/simple/densegraph_test.go index d3998946..34167501 100644 --- a/graph/simple/densegraph_test.go +++ b/graph/simple/densegraph_test.go @@ -73,25 +73,25 @@ func TestDirectedMatrix(t *testing.T) { testgraph.NodeExistence(t, directedMatrixBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, directedMatrixBuilder) + testgraph.ReturnAdjacentNodes(t, directedMatrixBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, directedMatrixBuilder) + testgraph.ReturnAllEdges(t, directedMatrixBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, directedMatrixBuilder) + testgraph.ReturnAllNodes(t, directedMatrixBuilder, true) }) t.Run("ReturnAllWeightedEdges", func(t *testing.T) { - testgraph.ReturnAllWeightedEdges(t, directedMatrixBuilder) + testgraph.ReturnAllWeightedEdges(t, directedMatrixBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, directedMatrixBuilder) + testgraph.ReturnEdgeSlice(t, directedMatrixBuilder, true) }) t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { - testgraph.ReturnWeightedEdgeSlice(t, directedMatrixBuilder) + testgraph.ReturnWeightedEdgeSlice(t, directedMatrixBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, directedMatrixBuilder) + testgraph.ReturnNodeSlice(t, directedMatrixBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, directedMatrixBuilder) @@ -142,25 +142,25 @@ func TestDirectedMatrixFrom(t *testing.T) { testgraph.NodeExistence(t, directedMatrixFromBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, directedMatrixFromBuilder) + testgraph.ReturnAdjacentNodes(t, directedMatrixFromBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, directedMatrixFromBuilder) + testgraph.ReturnAllEdges(t, directedMatrixFromBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, directedMatrixFromBuilder) + testgraph.ReturnAllNodes(t, directedMatrixFromBuilder, true) }) t.Run("ReturnAllWeightedEdges", func(t *testing.T) { - testgraph.ReturnAllWeightedEdges(t, directedMatrixFromBuilder) + testgraph.ReturnAllWeightedEdges(t, directedMatrixFromBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, directedMatrixFromBuilder) + testgraph.ReturnEdgeSlice(t, directedMatrixFromBuilder, true) }) t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { - testgraph.ReturnWeightedEdgeSlice(t, directedMatrixFromBuilder) + testgraph.ReturnWeightedEdgeSlice(t, directedMatrixFromBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, directedMatrixFromBuilder) + testgraph.ReturnNodeSlice(t, directedMatrixFromBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, directedMatrixFromBuilder) @@ -211,25 +211,25 @@ func TestUnirectedMatrix(t *testing.T) { testgraph.NodeExistence(t, undirectedMatrixBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, undirectedMatrixBuilder) + testgraph.ReturnAdjacentNodes(t, undirectedMatrixBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, undirectedMatrixBuilder) + testgraph.ReturnAllEdges(t, undirectedMatrixBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, undirectedMatrixBuilder) + testgraph.ReturnAllNodes(t, undirectedMatrixBuilder, true) }) t.Run("ReturnAllWeightedEdges", func(t *testing.T) { - testgraph.ReturnAllWeightedEdges(t, undirectedMatrixBuilder) + testgraph.ReturnAllWeightedEdges(t, undirectedMatrixBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, undirectedMatrixBuilder) + testgraph.ReturnEdgeSlice(t, undirectedMatrixBuilder, true) }) t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { - testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixBuilder) + testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, undirectedMatrixBuilder) + testgraph.ReturnNodeSlice(t, undirectedMatrixBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, undirectedMatrixBuilder) @@ -280,25 +280,25 @@ func TestUndirectedMatrixFrom(t *testing.T) { testgraph.NodeExistence(t, undirectedMatrixFromBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, undirectedMatrixFromBuilder) + testgraph.ReturnAdjacentNodes(t, undirectedMatrixFromBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, undirectedMatrixFromBuilder) + testgraph.ReturnAllEdges(t, undirectedMatrixFromBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, undirectedMatrixFromBuilder) + testgraph.ReturnAllNodes(t, undirectedMatrixFromBuilder, true) }) t.Run("ReturnAllWeightedEdges", func(t *testing.T) { - testgraph.ReturnAllWeightedEdges(t, undirectedMatrixFromBuilder) + testgraph.ReturnAllWeightedEdges(t, undirectedMatrixFromBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, undirectedMatrixFromBuilder) + testgraph.ReturnEdgeSlice(t, undirectedMatrixFromBuilder, true) }) t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { - testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixFromBuilder) + testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixFromBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, undirectedMatrixFromBuilder) + testgraph.ReturnNodeSlice(t, undirectedMatrixFromBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, undirectedMatrixFromBuilder) diff --git a/graph/simple/directed.go b/graph/simple/directed.go index d4c0eca0..f19efbd0 100644 --- a/graph/simple/directed.go +++ b/graph/simple/directed.go @@ -72,13 +72,16 @@ func (g *DirectedGraph) Edges() graph.Edges { edges = append(edges, e) } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *DirectedGraph) From(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } from := make([]graph.Node, len(g.from[id])) @@ -87,6 +90,9 @@ func (g *DirectedGraph) From(id int64) graph.Nodes { from[i] = g.nodes[vid] i++ } + if len(from) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(from) } @@ -134,7 +140,7 @@ func (g *DirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *DirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 @@ -213,7 +219,7 @@ func (g *DirectedGraph) SetEdge(e graph.Edge) { // To returns all nodes in g that can reach directly to n. func (g *DirectedGraph) To(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } to := make([]graph.Node, len(g.to[id])) @@ -222,5 +228,8 @@ func (g *DirectedGraph) To(id int64) graph.Nodes { to[i] = g.nodes[uid] i++ } + if len(to) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(to) } diff --git a/graph/simple/directed_test.go b/graph/simple/directed_test.go index cb66a8b4..051005ae 100644 --- a/graph/simple/directed_test.go +++ b/graph/simple/directed_test.go @@ -59,19 +59,19 @@ func TestDirected(t *testing.T) { testgraph.NodeExistence(t, directedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, directedBuilder) + testgraph.ReturnAdjacentNodes(t, directedBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, directedBuilder) + testgraph.ReturnAllEdges(t, directedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, directedBuilder) + testgraph.ReturnAllNodes(t, directedBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, directedBuilder) + testgraph.ReturnEdgeSlice(t, directedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, directedBuilder) + testgraph.ReturnNodeSlice(t, directedBuilder, true) }) } diff --git a/graph/simple/doc.go b/graph/simple/doc.go index a26818bd..dc3f24c5 100644 --- a/graph/simple/doc.go +++ b/graph/simple/doc.go @@ -4,4 +4,6 @@ // Package simple provides a suite of simple graph implementations satisfying // the gonum/graph interfaces. +// +// All types in simple return the graph.Empty value for empty iterators. package simple // import "gonum.org/v1/gonum/graph/simple" diff --git a/graph/simple/undirected.go b/graph/simple/undirected.go index 74e416d8..261d4bc3 100644 --- a/graph/simple/undirected.go +++ b/graph/simple/undirected.go @@ -69,7 +69,7 @@ func (g *UndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge { // Edges returns all the edges in the graph. func (g *UndirectedGraph) Edges() graph.Edges { if len(g.edges) == 0 { - return nil + return graph.Empty } var edges []graph.Edge seen := make(map[[2]int64]struct{}) @@ -85,13 +85,16 @@ func (g *UndirectedGraph) Edges() graph.Edges { edges = append(edges, e) } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *UndirectedGraph) From(id int64) graph.Nodes { if _, ok := g.nodes[id]; !ok { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.edges[id])) @@ -100,6 +103,9 @@ func (g *UndirectedGraph) From(id int64) graph.Nodes { nodes[i] = g.nodes[from] i++ } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -135,7 +141,7 @@ func (g *UndirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *UndirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 diff --git a/graph/simple/undirected_test.go b/graph/simple/undirected_test.go index b936f69b..02ff69c6 100644 --- a/graph/simple/undirected_test.go +++ b/graph/simple/undirected_test.go @@ -59,19 +59,19 @@ func TestUndirected(t *testing.T) { testgraph.NodeExistence(t, undirectedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, undirectedBuilder) + testgraph.ReturnAdjacentNodes(t, undirectedBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, undirectedBuilder) + testgraph.ReturnAllEdges(t, undirectedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, undirectedBuilder) + testgraph.ReturnAllNodes(t, undirectedBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, undirectedBuilder) + testgraph.ReturnEdgeSlice(t, undirectedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, undirectedBuilder) + testgraph.ReturnNodeSlice(t, undirectedBuilder, true) }) } diff --git a/graph/simple/weighted_directed.go b/graph/simple/weighted_directed.go index 58556a8e..92bd2842 100644 --- a/graph/simple/weighted_directed.go +++ b/graph/simple/weighted_directed.go @@ -76,13 +76,16 @@ func (g *WeightedDirectedGraph) Edges() graph.Edges { edges = append(edges, e) } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *WeightedDirectedGraph) From(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } from := make([]graph.Node, len(g.from[id])) @@ -91,6 +94,9 @@ func (g *WeightedDirectedGraph) From(id int64) graph.Nodes { from[i] = g.nodes[vid] i++ } + if len(from) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(from) } @@ -138,7 +144,7 @@ func (g *WeightedDirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *WeightedDirectedGraph) Nodes() graph.Nodes { if len(g.from) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 @@ -217,7 +223,7 @@ func (g *WeightedDirectedGraph) SetWeightedEdge(e graph.WeightedEdge) { // To returns all nodes in g that can reach directly to n. func (g *WeightedDirectedGraph) To(id int64) graph.Nodes { if _, ok := g.from[id]; !ok { - return nil + return graph.Empty } to := make([]graph.Node, len(g.to[id])) @@ -226,6 +232,9 @@ func (g *WeightedDirectedGraph) To(id int64) graph.Nodes { to[i] = g.nodes[uid] i++ } + if len(to) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(to) } @@ -263,5 +272,8 @@ func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges { edges = append(edges, e) } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedWeightedEdges(edges) } diff --git a/graph/simple/weighted_directed_test.go b/graph/simple/weighted_directed_test.go index 4acae122..90bd39dc 100644 --- a/graph/simple/weighted_directed_test.go +++ b/graph/simple/weighted_directed_test.go @@ -59,25 +59,25 @@ func TestWeightedDirected(t *testing.T) { testgraph.NodeExistence(t, weightedDirectedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder) + testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, weightedDirectedBuilder) + testgraph.ReturnAllEdges(t, weightedDirectedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, weightedDirectedBuilder) + testgraph.ReturnAllNodes(t, weightedDirectedBuilder, true) }) t.Run("ReturnAllWeightedEdges", func(t *testing.T) { - testgraph.ReturnAllWeightedEdges(t, weightedDirectedBuilder) + testgraph.ReturnAllWeightedEdges(t, weightedDirectedBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, weightedDirectedBuilder) + testgraph.ReturnEdgeSlice(t, weightedDirectedBuilder, true) }) t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { - testgraph.ReturnWeightedEdgeSlice(t, weightedDirectedBuilder) + testgraph.ReturnWeightedEdgeSlice(t, weightedDirectedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, weightedDirectedBuilder) + testgraph.ReturnNodeSlice(t, weightedDirectedBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, weightedDirectedBuilder) diff --git a/graph/simple/weighted_undirected.go b/graph/simple/weighted_undirected.go index 0efd8698..2b700745 100644 --- a/graph/simple/weighted_undirected.go +++ b/graph/simple/weighted_undirected.go @@ -73,7 +73,7 @@ func (g *WeightedUndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge { // Edges returns all the edges in the graph. func (g *WeightedUndirectedGraph) Edges() graph.Edges { if len(g.edges) == 0 { - return nil + return graph.Empty } var edges []graph.Edge seen := make(map[[2]int64]struct{}) @@ -89,13 +89,16 @@ func (g *WeightedUndirectedGraph) Edges() graph.Edges { edges = append(edges, e) } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedEdges(edges) } // From returns all nodes in g that can be reached directly from n. func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes { if _, ok := g.nodes[id]; !ok { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.edges[id])) @@ -104,6 +107,9 @@ func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes { nodes[i] = g.nodes[from] i++ } + if len(nodes) == 0 { + return graph.Empty + } return iterator.NewOrderedNodes(nodes) } @@ -139,7 +145,7 @@ func (g *WeightedUndirectedGraph) Node(id int64) graph.Node { // Nodes returns all the nodes in the graph. func (g *WeightedUndirectedGraph) Nodes() graph.Nodes { if len(g.nodes) == 0 { - return nil + return graph.Empty } nodes := make([]graph.Node, len(g.nodes)) i := 0 @@ -257,5 +263,8 @@ func (g *WeightedUndirectedGraph) WeightedEdges() graph.WeightedEdges { edges = append(edges, e) } } + if len(edges) == 0 { + return graph.Empty + } return iterator.NewOrderedWeightedEdges(edges) } diff --git a/graph/simple/weighted_undirected_test.go b/graph/simple/weighted_undirected_test.go index fe37d785..c8fa024d 100644 --- a/graph/simple/weighted_undirected_test.go +++ b/graph/simple/weighted_undirected_test.go @@ -59,25 +59,25 @@ func TestWeightedUndirected(t *testing.T) { testgraph.NodeExistence(t, weightedUndirectedBuilder) }) t.Run("ReturnAdjacentNodes", func(t *testing.T) { - testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder) + testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder, true) }) t.Run("ReturnAllEdges", func(t *testing.T) { - testgraph.ReturnAllEdges(t, weightedUndirectedBuilder) + testgraph.ReturnAllEdges(t, weightedUndirectedBuilder, true) }) t.Run("ReturnAllNodes", func(t *testing.T) { - testgraph.ReturnAllNodes(t, weightedUndirectedBuilder) + testgraph.ReturnAllNodes(t, weightedUndirectedBuilder, true) }) t.Run("ReturnAllWeightedEdges", func(t *testing.T) { - testgraph.ReturnAllWeightedEdges(t, weightedUndirectedBuilder) + testgraph.ReturnAllWeightedEdges(t, weightedUndirectedBuilder, true) }) t.Run("ReturnEdgeSlice", func(t *testing.T) { - testgraph.ReturnEdgeSlice(t, weightedUndirectedBuilder) + testgraph.ReturnEdgeSlice(t, weightedUndirectedBuilder, true) }) t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { - testgraph.ReturnWeightedEdgeSlice(t, weightedUndirectedBuilder) + testgraph.ReturnWeightedEdgeSlice(t, weightedUndirectedBuilder, true) }) t.Run("ReturnNodeSlice", func(t *testing.T) { - testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder) + testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder, true) }) t.Run("Weight", func(t *testing.T) { testgraph.Weight(t, weightedUndirectedBuilder) diff --git a/graph/testgraph/testgraph.go b/graph/testgraph/testgraph.go index 609007ee..7f06eb54 100644 --- a/graph/testgraph/testgraph.go +++ b/graph/testgraph/testgraph.go @@ -25,16 +25,23 @@ import ( // compared with NaN-awareness, so they may be NaN when there is no edge // associated with the Weight call. -// BUG(kortschak): The approach of using a nil return for empty sets of nodes -// and edges used prior to the introduction of the graph.Iterator types does -// not interact well with interfaces. For example, it is not possible to simply -// determine that an iterator is empty by calling it.Len without guarding that -// with a nil check. The validity of nil iterators may change depending on the -// outcome of https://github.com/gonum/gonum/issues/614. -func isValidIterator(graph.Iterator) bool { - // TODO(kortschak): Remove nil guards in iterator - // loops and slicer tests if this changes. - return true +func isValidIterator(it graph.Iterator) bool { + return it != nil +} + +func checkEmptyIterator(t *testing.T, it graph.Iterator, useEmpty bool) { + if it.Len() != 0 { + return + } + if it != graph.Empty { + if useEmpty { + t.Errorf("unexpected empty iterator: got:%T", it) + return + } + // Only log this since we say that a graph should + // return a graph.Empty when it is empty. + t.Logf("unexpected empty iterator: got:%T", it) + } } // A Builder function returns a graph constructed from the nodes, edges and @@ -68,7 +75,9 @@ type matrixer interface { // ReturnAllNodes tests the constructed graph for the ability to return all // the nodes it claims it has used in its construction. This is a check of // the Nodes method of graph.Graph and the iterator that is returned. -func ReturnAllNodes(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnAllNodes(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -81,8 +90,9 @@ func ReturnAllNodes(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } + checkEmptyIterator(t, it, useEmpty) var got []graph.Node - for it != nil && it.Next() { + for it.Next() { got = append(got, it.Node()) } @@ -99,7 +109,9 @@ func ReturnAllNodes(t *testing.T, b Builder) { // the nodes it claims it has used in its construction using the NodeSlicer // interface. This is a check of the Nodes method of graph.Graph and the // iterator that is returned. -func ReturnNodeSlice(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnNodeSlice(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -112,6 +124,7 @@ func ReturnNodeSlice(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } + checkEmptyIterator(t, it, useEmpty) if it == nil { continue } @@ -168,7 +181,9 @@ func NodeExistence(t *testing.T, b Builder) { // the Edges method of graph.Graph and the iterator that is returned. // ReturnAllEdges also checks that the edge end nodes exist within the graph, // checking the Node method of graph.Graph. -func ReturnAllEdges(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnAllEdges(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -184,7 +199,8 @@ func ReturnAllEdges(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } - for it != nil && it.Next() { + checkEmptyIterator(t, it, useEmpty) + for it.Next() { e := it.Edge() got = append(got, e) if g.Edge(e.From().ID(), e.To().ID()) == nil { @@ -212,7 +228,9 @@ func ReturnAllEdges(t *testing.T, b Builder) { // interface. This is a check of the Edges method of graph.Graph and the // iterator that is returned. ReturnEdgeSlice also checks that the edge end // nodes exist within the graph, checking the Node method of graph.Graph. -func ReturnEdgeSlice(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnEdgeSlice(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -228,6 +246,7 @@ func ReturnEdgeSlice(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } + checkEmptyIterator(t, it, useEmpty) if it == nil { continue } @@ -267,7 +286,9 @@ func ReturnEdgeSlice(t *testing.T, b Builder) { // // The edges used within and returned by the Builder function should be // graph.Line. The edge parameter passed to b will contain only graph.Line. -func ReturnAllLines(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnAllLines(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -283,6 +304,7 @@ func ReturnAllLines(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } + checkEmptyIterator(t, it, useEmpty) for _, e := range graph.EdgesOf(it) { if g.Edge(e.From().ID(), e.To().ID()) == nil { t.Errorf("missing edge for test %q: %v", test.name, e) @@ -294,11 +316,11 @@ func ReturnAllLines(t *testing.T, b Builder) { // and graph.Edges. switch lit := e.(type) { case graph.Lines: - for lit != nil && lit.Next() { + for lit.Next() { got = append(got, lit.Line()) } case graph.WeightedLines: - for lit != nil && lit.Next() { + for lit.Next() { got = append(got, lit.WeightedLine()) } default: @@ -331,7 +353,9 @@ func ReturnAllLines(t *testing.T, b Builder) { // The edges used within and returned by the Builder function should be // graph.WeightedEdge. The edge parameter passed to b will contain only // graph.WeightedEdge. -func ReturnAllWeightedEdges(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnAllWeightedEdges(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -347,7 +371,8 @@ func ReturnAllWeightedEdges(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } - for it != nil && it.Next() { + checkEmptyIterator(t, it, useEmpty) + for it.Next() { e := it.WeightedEdge() got = append(got, e) switch g := g.(type) { @@ -388,7 +413,9 @@ func ReturnAllWeightedEdges(t *testing.T, b Builder) { // The edges used within and returned by the Builder function should be // graph.WeightedEdge. The edge parameter passed to b will contain only // graph.WeightedEdge. -func ReturnWeightedEdgeSlice(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnWeightedEdgeSlice(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -404,6 +431,7 @@ func ReturnWeightedEdgeSlice(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } + checkEmptyIterator(t, it, useEmpty) s, ok := it.(graph.WeightedEdgeSlicer) if !ok { t.Errorf("invalid type for test %T: cannot return weighted edge slice", g) @@ -442,7 +470,9 @@ func ReturnWeightedEdgeSlice(t *testing.T, b Builder) { // The edges used within and returned by the Builder function should be // graph.WeightedLine. The edge parameter passed to b will contain only // graph.WeightedLine. -func ReturnAllWeightedLines(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnAllWeightedLines(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -458,6 +488,7 @@ func ReturnAllWeightedLines(t *testing.T, b Builder) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } + checkEmptyIterator(t, it, useEmpty) for _, e := range graph.WeightedEdgesOf(it) { if g.Edge(e.From().ID(), e.To().ID()) == nil { t.Errorf("missing edge for test %q: %v", test.name, e) @@ -469,11 +500,11 @@ func ReturnAllWeightedLines(t *testing.T, b Builder) { // and graph.Edges. switch lit := e.(type) { case graph.Lines: - for lit != nil && lit.Next() { + for lit.Next() { got = append(got, lit.Line()) } case graph.WeightedLines: - for lit != nil && lit.Next() { + for lit.Next() { got = append(got, lit.WeightedLine()) } default: @@ -602,7 +633,9 @@ func EdgeExistence(t *testing.T, b Builder) { // within the graph, checking the Node, Edge, EdgeBetween and HasEdgeBetween // methods of graph.Graph, the EdgeBetween method of graph.Undirected and the // HasEdgeFromTo method of graph.Directed. -func ReturnAdjacentNodes(t *testing.T, b Builder) { +// If useEmpty is true, graph iterators will be checked for the use of +// graph.Empty if they are empty. +func ReturnAdjacentNodes(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, nodes, edges, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { @@ -620,7 +653,12 @@ func ReturnAdjacentNodes(t *testing.T, b Builder) { // Test forward. u := x it := g.From(u.ID()) - for i := 0; it != nil && it.Next(); i++ { + if !isValidIterator(it) { + t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) + continue + } + checkEmptyIterator(t, it, useEmpty) + for i := 0; it.Next(); i++ { v := it.Node() if i == 0 && g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) @@ -645,7 +683,12 @@ func ReturnAdjacentNodes(t *testing.T, b Builder) { // Test backward. v := x it = g.To(v.ID()) - for i := 0; it != nil && it.Next(); i++ { + if !isValidIterator(it) { + t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) + continue + } + checkEmptyIterator(t, it, useEmpty) + for i := 0; it.Next(); i++ { u := it.Node() if i == 0 && g.Node(v.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, v.ID()) @@ -673,7 +716,12 @@ func ReturnAdjacentNodes(t *testing.T, b Builder) { case graph.Undirected: u := x it := g.From(u.ID()) - for i := 0; it != nil && it.Next(); i++ { + if !isValidIterator(it) { + t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) + continue + } + checkEmptyIterator(t, it, useEmpty) + for i := 0; it.Next(); i++ { v := it.Node() if i == 0 && g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) @@ -699,7 +747,12 @@ func ReturnAdjacentNodes(t *testing.T, b Builder) { default: u := x it := g.From(u.ID()) - for i := 0; it != nil && it.Next(); i++ { + if !isValidIterator(it) { + t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) + continue + } + checkEmptyIterator(t, it, useEmpty) + for i := 0; it.Next(); i++ { v := it.Node() if i == 0 && g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) diff --git a/graph/topo/johnson_cycles.go b/graph/topo/johnson_cycles.go index 48fde064..8a78ba2f 100644 --- a/graph/topo/johnson_cycles.go +++ b/graph/topo/johnson_cycles.go @@ -252,7 +252,7 @@ func (g johnsonGraph) Nodes() graph.Nodes { func (g johnsonGraph) From(id int64) graph.Nodes { adj := g.succ[id] if len(adj) == 0 { - return nil + return graph.Empty } succ := make([]graph.Node, 0, len(adj)) for id := range adj {