graph: add Empty universal iterator for empty returns

This commit is contained in:
Dan Kortschak
2018-12-13 07:56:04 +10:30
parent fd50e23eae
commit 14c7f9569f
29 changed files with 362 additions and 140 deletions

View File

@@ -3,4 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package graph defines graph interfaces. // 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" package graph // import "gonum.org/v1/gonum/graph"

View File

@@ -32,10 +32,14 @@ type Graph interface {
Node(id int64) Node Node(id int64) Node
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
//
// Nodes must not return nil.
Nodes() Nodes Nodes() Nodes
// From returns all nodes that can be reached directly // From returns all nodes that can be reached directly
// from the node with the given ID. // from the node with the given ID.
//
// From must not return nil.
From(id int64) Nodes From(id int64) Nodes
// HasEdgeBetween returns whether an edge exists between // HasEdgeBetween returns whether an edge exists between
@@ -99,6 +103,8 @@ type Directed interface {
// To returns all nodes that can reach directly // To returns all nodes that can reach directly
// to the node with the given ID. // to the node with the given ID.
//
// To must not return nil.
To(id int64) Nodes To(id int64) Nodes
} }
@@ -113,6 +119,8 @@ type WeightedDirected interface {
// To returns all nodes that can reach directly // To returns all nodes that can reach directly
// to the node with the given ID. // to the node with the given ID.
//
// To must not return nil.
To(id int64) Nodes To(id int64) Nodes
} }

View File

@@ -73,7 +73,7 @@ func (g *DirectedGraph) Edge(uid, vid int64) graph.Edge {
// is a multi.Edge. // is a multi.Edge.
func (g *DirectedGraph) Edges() graph.Edges { func (g *DirectedGraph) Edges() graph.Edges {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
var edges []graph.Edge var edges []graph.Edge
for _, u := range g.nodes { 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) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *DirectedGraph) From(id int64) graph.Nodes { func (g *DirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
from := make([]graph.Node, len(g.from[id])) 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] from[i] = g.nodes[vid]
i++ i++
} }
if len(from) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(from) 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 { func (g *DirectedGraph) Lines(uid, vid int64) graph.Lines {
edge := g.from[uid][vid] edge := g.from[uid][vid]
if len(edge) == 0 { if len(edge) == 0 {
return nil return graph.Empty
} }
var lines []graph.Line var lines []graph.Line
for _, l := range edge { for _, l := range edge {
@@ -167,7 +173,7 @@ func (g *DirectedGraph) Node(id int64) graph.Node {
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
func (g *DirectedGraph) Nodes() graph.Nodes { func (g *DirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 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. // To returns all nodes in g that can reach directly to n.
func (g *DirectedGraph) To(id int64) graph.Nodes { func (g *DirectedGraph) To(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
to := make([]graph.Node, len(g.to[id])) 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] to[i] = g.nodes[uid]
i++ i++
} }
if len(to) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(to) return iterator.NewOrderedNodes(to)
} }

View File

@@ -53,16 +53,16 @@ func TestDirected(t *testing.T) {
testgraph.NodeExistence(t, directedBuilder) testgraph.NodeExistence(t, directedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, directedBuilder) testgraph.ReturnAdjacentNodes(t, directedBuilder, true)
}) })
t.Run("ReturnAllLines", func(t *testing.T) { t.Run("ReturnAllLines", func(t *testing.T) {
testgraph.ReturnAllLines(t, directedBuilder) testgraph.ReturnAllLines(t, directedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, directedBuilder) testgraph.ReturnAllNodes(t, directedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, directedBuilder) testgraph.ReturnNodeSlice(t, directedBuilder, true)
}) })
} }

View File

@@ -4,4 +4,6 @@
// Package multi provides a suite of multigraph implementations satisfying // Package multi provides a suite of multigraph implementations satisfying
// the gonum/graph interfaces. // 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" package multi // import "gonum.org/v1/gonum/graph/multi"

View File

@@ -75,7 +75,7 @@ func (g *UndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge {
// is a multi.Edge. // is a multi.Edge.
func (g *UndirectedGraph) Edges() graph.Edges { func (g *UndirectedGraph) Edges() graph.Edges {
if len(g.lines) == 0 { if len(g.lines) == 0 {
return nil return graph.Empty
} }
var edges []graph.Edge var edges []graph.Edge
seen := make(map[int64]struct{}) 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) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *UndirectedGraph) From(id int64) graph.Nodes { func (g *UndirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.nodes[id]; !ok { if _, ok := g.nodes[id]; !ok {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.lines[id])) 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] nodes[i] = g.nodes[from]
i++ i++
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) 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. // LinesBetween returns the lines between nodes x and y.
func (g *UndirectedGraph) LinesBetween(xid, yid int64) graph.Lines { func (g *UndirectedGraph) LinesBetween(xid, yid int64) graph.Lines {
if !g.HasEdgeBetween(xid, yid) { if !g.HasEdgeBetween(xid, yid) {
return nil return graph.Empty
} }
var lines []graph.Line var lines []graph.Line
for _, l := range g.lines[xid][yid] { 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. // Nodes returns all the nodes in the graph.
func (g *UndirectedGraph) Nodes() graph.Nodes { func (g *UndirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 i := 0

View File

@@ -53,16 +53,16 @@ func TestUndirected(t *testing.T) {
testgraph.NodeExistence(t, undirectedBuilder) testgraph.NodeExistence(t, undirectedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, undirectedBuilder) testgraph.ReturnAdjacentNodes(t, undirectedBuilder, true)
}) })
t.Run("ReturnAllLines", func(t *testing.T) { t.Run("ReturnAllLines", func(t *testing.T) {
testgraph.ReturnAllLines(t, undirectedBuilder) testgraph.ReturnAllLines(t, undirectedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, undirectedBuilder) testgraph.ReturnAllNodes(t, undirectedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, undirectedBuilder) testgraph.ReturnNodeSlice(t, undirectedBuilder, true)
}) })
} }

View File

@@ -78,7 +78,7 @@ func (g *WeightedDirectedGraph) Edge(uid, vid int64) graph.Edge {
// is a multi.WeightedEdge. // is a multi.WeightedEdge.
func (g *WeightedDirectedGraph) Edges() graph.Edges { func (g *WeightedDirectedGraph) Edges() graph.Edges {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
var edges []graph.Edge var edges []graph.Edge
for _, u := range g.nodes { 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) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *WeightedDirectedGraph) From(id int64) graph.Nodes { func (g *WeightedDirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
from := make([]graph.Node, len(g.from[id])) 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] from[i] = g.nodes[vid]
i++ i++
} }
if len(from) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(from) 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 { func (g *WeightedDirectedGraph) Lines(uid, vid int64) graph.Lines {
edge := g.from[uid][vid] edge := g.from[uid][vid]
if len(edge) == 0 { if len(edge) == 0 {
return nil return graph.Empty
} }
var lines []graph.Line var lines []graph.Line
for _, l := range edge { for _, l := range edge {
@@ -175,7 +181,7 @@ func (g *WeightedDirectedGraph) Node(id int64) graph.Node {
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
func (g *WeightedDirectedGraph) Nodes() graph.Nodes { func (g *WeightedDirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 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. // To returns all nodes in g that can reach directly to n.
func (g *WeightedDirectedGraph) To(id int64) graph.Nodes { func (g *WeightedDirectedGraph) To(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
to := make([]graph.Node, len(g.to[id])) 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] to[i] = g.nodes[uid]
i++ i++
} }
if len(to) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(to) return iterator.NewOrderedNodes(to)
} }
@@ -303,7 +312,7 @@ func (g *WeightedDirectedGraph) WeightedEdge(uid, vid int64) graph.WeightedEdge
// is a multi.WeightedEdge. // is a multi.WeightedEdge.
func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges { func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
var edges []graph.WeightedEdge var edges []graph.WeightedEdge
for _, u := range g.nodes { 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) return iterator.NewOrderedWeightedEdges(edges)
} }
@@ -330,7 +342,7 @@ func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges {
func (g *WeightedDirectedGraph) WeightedLines(uid, vid int64) graph.WeightedLines { func (g *WeightedDirectedGraph) WeightedLines(uid, vid int64) graph.WeightedLines {
edge := g.from[uid][vid] edge := g.from[uid][vid]
if len(edge) == 0 { if len(edge) == 0 {
return nil return graph.Empty
} }
var lines []graph.WeightedLine var lines []graph.WeightedLine
for _, l := range edge { for _, l := range edge {

View File

@@ -65,19 +65,19 @@ func TestWeightedDirected(t *testing.T) {
testgraph.NodeExistence(t, weightedDirectedBuilder) testgraph.NodeExistence(t, weightedDirectedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder) testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnAllLines", func(t *testing.T) { t.Run("ReturnAllLines", func(t *testing.T) {
testgraph.ReturnAllLines(t, weightedDirectedBuilder) testgraph.ReturnAllLines(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, weightedDirectedBuilder) testgraph.ReturnAllNodes(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnAllWeightedLines", func(t *testing.T) { t.Run("ReturnAllWeightedLines", func(t *testing.T) {
testgraph.ReturnAllWeightedLines(t, weightedDirectedBuilder) testgraph.ReturnAllWeightedLines(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, weightedDirectedBuilder) testgraph.ReturnNodeSlice(t, weightedDirectedBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, weightedDirectedBuilder) testgraph.Weight(t, weightedDirectedBuilder)

View File

@@ -80,7 +80,7 @@ func (g *WeightedUndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge {
// is a multi.Edge. // is a multi.Edge.
func (g *WeightedUndirectedGraph) Edges() graph.Edges { func (g *WeightedUndirectedGraph) Edges() graph.Edges {
if len(g.lines) == 0 { if len(g.lines) == 0 {
return nil return graph.Empty
} }
var edges []graph.Edge var edges []graph.Edge
seen := make(map[int64]struct{}) 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) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes { func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.nodes[id]; !ok { if _, ok := g.nodes[id]; !ok {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.lines[id])) 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] nodes[i] = g.nodes[from]
i++ i++
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) 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 { func (g *WeightedUndirectedGraph) LinesBetween(xid, yid int64) graph.Lines {
edge := g.lines[xid][yid] edge := g.lines[xid][yid]
if len(edge) == 0 { if len(edge) == 0 {
return nil return graph.Empty
} }
var lines []graph.Line var lines []graph.Line
seen := make(map[int64]struct{}) 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. // Nodes returns all the nodes in the graph.
func (g *WeightedUndirectedGraph) Nodes() graph.Nodes { func (g *WeightedUndirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 i := 0
@@ -291,7 +297,7 @@ func (g *WeightedUndirectedGraph) WeightedEdgeBetween(xid, yid int64) graph.Weig
// is a multi.Edge. // is a multi.Edge.
func (g *WeightedUndirectedGraph) WeightedEdges() graph.WeightedEdges { func (g *WeightedUndirectedGraph) WeightedEdges() graph.WeightedEdges {
if len(g.lines) == 0 { if len(g.lines) == 0 {
return nil return graph.Empty
} }
var edges []graph.WeightedEdge var edges []graph.WeightedEdge
seen := make(map[int64]struct{}) 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) 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 { func (g *WeightedUndirectedGraph) WeightedLinesBetween(xid, yid int64) graph.WeightedLines {
edge := g.lines[xid][yid] edge := g.lines[xid][yid]
if len(edge) == 0 { if len(edge) == 0 {
return nil return graph.Empty
} }
var lines []graph.WeightedLine var lines []graph.WeightedLine
seen := make(map[int64]struct{}) seen := make(map[int64]struct{})

View File

@@ -65,19 +65,19 @@ func TestWeightedUndirected(t *testing.T) {
testgraph.NodeExistence(t, weightedUndirectedBuilder) testgraph.NodeExistence(t, weightedUndirectedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder) testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnAllLines", func(t *testing.T) { t.Run("ReturnAllLines", func(t *testing.T) {
testgraph.ReturnAllLines(t, weightedUndirectedBuilder) testgraph.ReturnAllLines(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, weightedUndirectedBuilder) testgraph.ReturnAllNodes(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnAllWeightedLines", func(t *testing.T) { t.Run("ReturnAllWeightedLines", func(t *testing.T) {
testgraph.ReturnAllWeightedLines(t, weightedUndirectedBuilder) testgraph.ReturnAllWeightedLines(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder) testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, weightedUndirectedBuilder) testgraph.Weight(t, weightedUndirectedBuilder)

View File

@@ -24,10 +24,14 @@ type Multigraph interface {
Node(id int64) Node Node(id int64) Node
// Nodes returns all the nodes in the multigraph. // Nodes returns all the nodes in the multigraph.
//
// Nodes must not return nil.
Nodes() Nodes Nodes() Nodes
// From returns all nodes that can be reached directly // From returns all nodes that can be reached directly
// from the node with the given ID. // from the node with the given ID.
//
// From must not return nil.
From(id int64) Nodes From(id int64) Nodes
// HasEdgeBetween returns whether an edge exists between // HasEdgeBetween returns whether an edge exists between
@@ -38,6 +42,8 @@ type Multigraph interface {
// vid, if any such lines exist and nil otherwise. The // vid, if any such lines exist and nil otherwise. The
// node v must be directly reachable from u as defined by // node v must be directly reachable from u as defined by
// the From method. // the From method.
//
// Lines must not return nil.
Lines(uid, vid int64) Lines Lines(uid, vid int64) Lines
} }
@@ -49,6 +55,8 @@ type WeightedMultigraph interface {
// with IDs uid and vid if any such lines exist and nil // with IDs uid and vid if any such lines exist and nil
// otherwise. The node v must be directly reachable // otherwise. The node v must be directly reachable
// from u as defined by the From method. // from u as defined by the From method.
//
// WeightedLines must not return nil.
WeightedLines(uid, vid int64) WeightedLines WeightedLines(uid, vid int64) WeightedLines
} }
@@ -58,6 +66,8 @@ type UndirectedMultigraph interface {
// LinesBetween returns the lines between nodes x and y // LinesBetween returns the lines between nodes x and y
// with IDs xid and yid. // with IDs xid and yid.
//
// LinesBetween must not return nil.
LinesBetween(xid, yid int64) Lines LinesBetween(xid, yid int64) Lines
} }
@@ -67,6 +77,8 @@ type WeightedUndirectedMultigraph interface {
// WeightedLinesBetween returns the lines between nodes // WeightedLinesBetween returns the lines between nodes
// x and y with IDs xid and yid. // x and y with IDs xid and yid.
//
// WeightedLinesBetween must not return nil.
WeightedLinesBetween(xid, yid int64) WeightedLines WeightedLinesBetween(xid, yid int64) WeightedLines
} }
@@ -81,6 +93,8 @@ type DirectedMultigraph interface {
// To returns all nodes that can reach directly // To returns all nodes that can reach directly
// to the node with the given ID. // to the node with the given ID.
//
// To must not return nil.
To(id int64) Nodes To(id int64) Nodes
} }
@@ -95,6 +109,8 @@ type WeightedDirectedMultigraph interface {
// To returns all nodes that can reach directly // To returns all nodes that can reach directly
// to the node with the given ID. // to the node with the given ID.
//
// To must not return nil.
To(id int64) Nodes To(id int64) Nodes
} }

View File

@@ -227,3 +227,40 @@ func WeightedLinesOf(it WeightedLines) []WeightedLine {
} }
return l 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 }

View File

@@ -169,7 +169,7 @@ func (g *Grid) NodeAt(r, c int) graph.Node {
// ends of an edge must be open. // ends of an edge must be open.
func (g *Grid) From(uid int64) graph.Nodes { func (g *Grid) From(uid int64) graph.Nodes {
if !g.HasOpen(uid) { if !g.HasOpen(uid) {
return nil return graph.Empty
} }
nr, nc := g.RowCol(uid) nr, nc := g.RowCol(uid)
var to []graph.Node 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) return iterator.NewOrderedNodes(to)
} }

View File

@@ -178,7 +178,7 @@ func (l *LimitedVisionGrid) has(id int64) bool {
// From returns nodes that are optimistically reachable from u. // From returns nodes that are optimistically reachable from u.
func (l *LimitedVisionGrid) From(uid int64) graph.Nodes { func (l *LimitedVisionGrid) From(uid int64) graph.Nodes {
if !l.has(uid) { if !l.has(uid) {
return nil return graph.Empty
} }
nr, nc := l.RowCol(uid) 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) return iterator.NewOrderedNodes(to)
} }

View File

@@ -93,13 +93,16 @@ func (g *DirectedMatrix) Edges() graph.Edges {
} }
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedEdges(edges) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *DirectedMatrix) From(id int64) graph.Nodes { func (g *DirectedMatrix) From(id int64) graph.Nodes {
if !g.has(id) { if !g.has(id) {
return nil return graph.Empty
} }
var nodes []graph.Node var nodes []graph.Node
_, c := g.mat.Dims() _, c := g.mat.Dims()
@@ -112,6 +115,9 @@ func (g *DirectedMatrix) From(id int64) graph.Nodes {
nodes = append(nodes, g.Node(int64(j))) nodes = append(nodes, g.Node(int64(j)))
} }
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) return iterator.NewOrderedNodes(nodes)
} }
@@ -169,6 +175,7 @@ func (g *DirectedMatrix) Nodes() graph.Nodes {
return iterator.NewOrderedNodes(nodes) return iterator.NewOrderedNodes(nodes)
} }
r, _ := g.mat.Dims() r, _ := g.mat.Dims()
// Matrix graphs must have at least one node.
return iterator.NewImplicitNodes(0, r, newSimpleNode) 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. // To returns all nodes in g that can reach directly to n.
func (g *DirectedMatrix) To(id int64) graph.Nodes { func (g *DirectedMatrix) To(id int64) graph.Nodes {
if !g.has(id) { if !g.has(id) {
return nil return graph.Empty
} }
var nodes []graph.Node var nodes []graph.Node
r, _ := g.mat.Dims() r, _ := g.mat.Dims()
@@ -237,6 +244,9 @@ func (g *DirectedMatrix) To(id int64) graph.Nodes {
nodes = append(nodes, g.Node(int64(i))) nodes = append(nodes, g.Node(int64(i)))
} }
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) 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) return iterator.NewOrderedWeightedEdges(edges)
} }

View File

@@ -95,13 +95,16 @@ func (g *UndirectedMatrix) Edges() graph.Edges {
} }
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedEdges(edges) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *UndirectedMatrix) From(id int64) graph.Nodes { func (g *UndirectedMatrix) From(id int64) graph.Nodes {
if !g.has(id) { if !g.has(id) {
return nil return graph.Empty
} }
var nodes []graph.Node var nodes []graph.Node
r := g.mat.Symmetric() r := g.mat.Symmetric()
@@ -114,6 +117,9 @@ func (g *UndirectedMatrix) From(id int64) graph.Nodes {
nodes = append(nodes, g.Node(int64(i))) nodes = append(nodes, g.Node(int64(i)))
} }
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) return iterator.NewOrderedNodes(nodes)
} }
@@ -156,6 +162,7 @@ func (g *UndirectedMatrix) Nodes() graph.Nodes {
return iterator.NewOrderedNodes(nodes) return iterator.NewOrderedNodes(nodes)
} }
r := g.mat.Symmetric() r := g.mat.Symmetric()
// Matrix graphs must have at least one node.
return iterator.NewImplicitNodes(0, r, newSimpleNode) 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) return iterator.NewOrderedWeightedEdges(edges)
} }

View File

@@ -73,25 +73,25 @@ func TestDirectedMatrix(t *testing.T) {
testgraph.NodeExistence(t, directedMatrixBuilder) testgraph.NodeExistence(t, directedMatrixBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, directedMatrixBuilder) testgraph.ReturnAdjacentNodes(t, directedMatrixBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, directedMatrixBuilder) testgraph.ReturnAllEdges(t, directedMatrixBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, directedMatrixBuilder) testgraph.ReturnAllNodes(t, directedMatrixBuilder, true)
}) })
t.Run("ReturnAllWeightedEdges", func(t *testing.T) { t.Run("ReturnAllWeightedEdges", func(t *testing.T) {
testgraph.ReturnAllWeightedEdges(t, directedMatrixBuilder) testgraph.ReturnAllWeightedEdges(t, directedMatrixBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, directedMatrixBuilder) testgraph.ReturnEdgeSlice(t, directedMatrixBuilder, true)
}) })
t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) {
testgraph.ReturnWeightedEdgeSlice(t, directedMatrixBuilder) testgraph.ReturnWeightedEdgeSlice(t, directedMatrixBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, directedMatrixBuilder) testgraph.ReturnNodeSlice(t, directedMatrixBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, directedMatrixBuilder) testgraph.Weight(t, directedMatrixBuilder)
@@ -142,25 +142,25 @@ func TestDirectedMatrixFrom(t *testing.T) {
testgraph.NodeExistence(t, directedMatrixFromBuilder) testgraph.NodeExistence(t, directedMatrixFromBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, directedMatrixFromBuilder) testgraph.ReturnAdjacentNodes(t, directedMatrixFromBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, directedMatrixFromBuilder) testgraph.ReturnAllEdges(t, directedMatrixFromBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, directedMatrixFromBuilder) testgraph.ReturnAllNodes(t, directedMatrixFromBuilder, true)
}) })
t.Run("ReturnAllWeightedEdges", func(t *testing.T) { t.Run("ReturnAllWeightedEdges", func(t *testing.T) {
testgraph.ReturnAllWeightedEdges(t, directedMatrixFromBuilder) testgraph.ReturnAllWeightedEdges(t, directedMatrixFromBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, directedMatrixFromBuilder) testgraph.ReturnEdgeSlice(t, directedMatrixFromBuilder, true)
}) })
t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) {
testgraph.ReturnWeightedEdgeSlice(t, directedMatrixFromBuilder) testgraph.ReturnWeightedEdgeSlice(t, directedMatrixFromBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, directedMatrixFromBuilder) testgraph.ReturnNodeSlice(t, directedMatrixFromBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, directedMatrixFromBuilder) testgraph.Weight(t, directedMatrixFromBuilder)
@@ -211,25 +211,25 @@ func TestUnirectedMatrix(t *testing.T) {
testgraph.NodeExistence(t, undirectedMatrixBuilder) testgraph.NodeExistence(t, undirectedMatrixBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, undirectedMatrixBuilder) testgraph.ReturnAdjacentNodes(t, undirectedMatrixBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, undirectedMatrixBuilder) testgraph.ReturnAllEdges(t, undirectedMatrixBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, undirectedMatrixBuilder) testgraph.ReturnAllNodes(t, undirectedMatrixBuilder, true)
}) })
t.Run("ReturnAllWeightedEdges", func(t *testing.T) { t.Run("ReturnAllWeightedEdges", func(t *testing.T) {
testgraph.ReturnAllWeightedEdges(t, undirectedMatrixBuilder) testgraph.ReturnAllWeightedEdges(t, undirectedMatrixBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, undirectedMatrixBuilder) testgraph.ReturnEdgeSlice(t, undirectedMatrixBuilder, true)
}) })
t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) {
testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixBuilder) testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, undirectedMatrixBuilder) testgraph.ReturnNodeSlice(t, undirectedMatrixBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, undirectedMatrixBuilder) testgraph.Weight(t, undirectedMatrixBuilder)
@@ -280,25 +280,25 @@ func TestUndirectedMatrixFrom(t *testing.T) {
testgraph.NodeExistence(t, undirectedMatrixFromBuilder) testgraph.NodeExistence(t, undirectedMatrixFromBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, undirectedMatrixFromBuilder) testgraph.ReturnAdjacentNodes(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, undirectedMatrixFromBuilder) testgraph.ReturnAllEdges(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, undirectedMatrixFromBuilder) testgraph.ReturnAllNodes(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("ReturnAllWeightedEdges", func(t *testing.T) { t.Run("ReturnAllWeightedEdges", func(t *testing.T) {
testgraph.ReturnAllWeightedEdges(t, undirectedMatrixFromBuilder) testgraph.ReturnAllWeightedEdges(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, undirectedMatrixFromBuilder) testgraph.ReturnEdgeSlice(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) {
testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixFromBuilder) testgraph.ReturnWeightedEdgeSlice(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, undirectedMatrixFromBuilder) testgraph.ReturnNodeSlice(t, undirectedMatrixFromBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, undirectedMatrixFromBuilder) testgraph.Weight(t, undirectedMatrixFromBuilder)

View File

@@ -72,13 +72,16 @@ func (g *DirectedGraph) Edges() graph.Edges {
edges = append(edges, e) edges = append(edges, e)
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedEdges(edges) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *DirectedGraph) From(id int64) graph.Nodes { func (g *DirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
from := make([]graph.Node, len(g.from[id])) 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] from[i] = g.nodes[vid]
i++ i++
} }
if len(from) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(from) return iterator.NewOrderedNodes(from)
} }
@@ -134,7 +140,7 @@ func (g *DirectedGraph) Node(id int64) graph.Node {
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
func (g *DirectedGraph) Nodes() graph.Nodes { func (g *DirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 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. // To returns all nodes in g that can reach directly to n.
func (g *DirectedGraph) To(id int64) graph.Nodes { func (g *DirectedGraph) To(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
to := make([]graph.Node, len(g.to[id])) 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] to[i] = g.nodes[uid]
i++ i++
} }
if len(to) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(to) return iterator.NewOrderedNodes(to)
} }

View File

@@ -59,19 +59,19 @@ func TestDirected(t *testing.T) {
testgraph.NodeExistence(t, directedBuilder) testgraph.NodeExistence(t, directedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, directedBuilder) testgraph.ReturnAdjacentNodes(t, directedBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, directedBuilder) testgraph.ReturnAllEdges(t, directedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, directedBuilder) testgraph.ReturnAllNodes(t, directedBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, directedBuilder) testgraph.ReturnEdgeSlice(t, directedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, directedBuilder) testgraph.ReturnNodeSlice(t, directedBuilder, true)
}) })
} }

View File

@@ -4,4 +4,6 @@
// Package simple provides a suite of simple graph implementations satisfying // Package simple provides a suite of simple graph implementations satisfying
// the gonum/graph interfaces. // 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" package simple // import "gonum.org/v1/gonum/graph/simple"

View File

@@ -69,7 +69,7 @@ func (g *UndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge {
// Edges returns all the edges in the graph. // Edges returns all the edges in the graph.
func (g *UndirectedGraph) Edges() graph.Edges { func (g *UndirectedGraph) Edges() graph.Edges {
if len(g.edges) == 0 { if len(g.edges) == 0 {
return nil return graph.Empty
} }
var edges []graph.Edge var edges []graph.Edge
seen := make(map[[2]int64]struct{}) seen := make(map[[2]int64]struct{})
@@ -85,13 +85,16 @@ func (g *UndirectedGraph) Edges() graph.Edges {
edges = append(edges, e) edges = append(edges, e)
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedEdges(edges) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *UndirectedGraph) From(id int64) graph.Nodes { func (g *UndirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.nodes[id]; !ok { if _, ok := g.nodes[id]; !ok {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.edges[id])) 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] nodes[i] = g.nodes[from]
i++ i++
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) return iterator.NewOrderedNodes(nodes)
} }
@@ -135,7 +141,7 @@ func (g *UndirectedGraph) Node(id int64) graph.Node {
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
func (g *UndirectedGraph) Nodes() graph.Nodes { func (g *UndirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 i := 0

View File

@@ -59,19 +59,19 @@ func TestUndirected(t *testing.T) {
testgraph.NodeExistence(t, undirectedBuilder) testgraph.NodeExistence(t, undirectedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, undirectedBuilder) testgraph.ReturnAdjacentNodes(t, undirectedBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, undirectedBuilder) testgraph.ReturnAllEdges(t, undirectedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, undirectedBuilder) testgraph.ReturnAllNodes(t, undirectedBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, undirectedBuilder) testgraph.ReturnEdgeSlice(t, undirectedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, undirectedBuilder) testgraph.ReturnNodeSlice(t, undirectedBuilder, true)
}) })
} }

View File

@@ -76,13 +76,16 @@ func (g *WeightedDirectedGraph) Edges() graph.Edges {
edges = append(edges, e) edges = append(edges, e)
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedEdges(edges) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *WeightedDirectedGraph) From(id int64) graph.Nodes { func (g *WeightedDirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
from := make([]graph.Node, len(g.from[id])) 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] from[i] = g.nodes[vid]
i++ i++
} }
if len(from) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(from) return iterator.NewOrderedNodes(from)
} }
@@ -138,7 +144,7 @@ func (g *WeightedDirectedGraph) Node(id int64) graph.Node {
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
func (g *WeightedDirectedGraph) Nodes() graph.Nodes { func (g *WeightedDirectedGraph) Nodes() graph.Nodes {
if len(g.from) == 0 { if len(g.from) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 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. // To returns all nodes in g that can reach directly to n.
func (g *WeightedDirectedGraph) To(id int64) graph.Nodes { func (g *WeightedDirectedGraph) To(id int64) graph.Nodes {
if _, ok := g.from[id]; !ok { if _, ok := g.from[id]; !ok {
return nil return graph.Empty
} }
to := make([]graph.Node, len(g.to[id])) 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] to[i] = g.nodes[uid]
i++ i++
} }
if len(to) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(to) return iterator.NewOrderedNodes(to)
} }
@@ -263,5 +272,8 @@ func (g *WeightedDirectedGraph) WeightedEdges() graph.WeightedEdges {
edges = append(edges, e) edges = append(edges, e)
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedWeightedEdges(edges) return iterator.NewOrderedWeightedEdges(edges)
} }

View File

@@ -59,25 +59,25 @@ func TestWeightedDirected(t *testing.T) {
testgraph.NodeExistence(t, weightedDirectedBuilder) testgraph.NodeExistence(t, weightedDirectedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder) testgraph.ReturnAdjacentNodes(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, weightedDirectedBuilder) testgraph.ReturnAllEdges(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, weightedDirectedBuilder) testgraph.ReturnAllNodes(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnAllWeightedEdges", func(t *testing.T) { t.Run("ReturnAllWeightedEdges", func(t *testing.T) {
testgraph.ReturnAllWeightedEdges(t, weightedDirectedBuilder) testgraph.ReturnAllWeightedEdges(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, weightedDirectedBuilder) testgraph.ReturnEdgeSlice(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) {
testgraph.ReturnWeightedEdgeSlice(t, weightedDirectedBuilder) testgraph.ReturnWeightedEdgeSlice(t, weightedDirectedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, weightedDirectedBuilder) testgraph.ReturnNodeSlice(t, weightedDirectedBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, weightedDirectedBuilder) testgraph.Weight(t, weightedDirectedBuilder)

View File

@@ -73,7 +73,7 @@ func (g *WeightedUndirectedGraph) EdgeBetween(xid, yid int64) graph.Edge {
// Edges returns all the edges in the graph. // Edges returns all the edges in the graph.
func (g *WeightedUndirectedGraph) Edges() graph.Edges { func (g *WeightedUndirectedGraph) Edges() graph.Edges {
if len(g.edges) == 0 { if len(g.edges) == 0 {
return nil return graph.Empty
} }
var edges []graph.Edge var edges []graph.Edge
seen := make(map[[2]int64]struct{}) seen := make(map[[2]int64]struct{})
@@ -89,13 +89,16 @@ func (g *WeightedUndirectedGraph) Edges() graph.Edges {
edges = append(edges, e) edges = append(edges, e)
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedEdges(edges) return iterator.NewOrderedEdges(edges)
} }
// From returns all nodes in g that can be reached directly from n. // From returns all nodes in g that can be reached directly from n.
func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes { func (g *WeightedUndirectedGraph) From(id int64) graph.Nodes {
if _, ok := g.nodes[id]; !ok { if _, ok := g.nodes[id]; !ok {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.edges[id])) 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] nodes[i] = g.nodes[from]
i++ i++
} }
if len(nodes) == 0 {
return graph.Empty
}
return iterator.NewOrderedNodes(nodes) return iterator.NewOrderedNodes(nodes)
} }
@@ -139,7 +145,7 @@ func (g *WeightedUndirectedGraph) Node(id int64) graph.Node {
// Nodes returns all the nodes in the graph. // Nodes returns all the nodes in the graph.
func (g *WeightedUndirectedGraph) Nodes() graph.Nodes { func (g *WeightedUndirectedGraph) Nodes() graph.Nodes {
if len(g.nodes) == 0 { if len(g.nodes) == 0 {
return nil return graph.Empty
} }
nodes := make([]graph.Node, len(g.nodes)) nodes := make([]graph.Node, len(g.nodes))
i := 0 i := 0
@@ -257,5 +263,8 @@ func (g *WeightedUndirectedGraph) WeightedEdges() graph.WeightedEdges {
edges = append(edges, e) edges = append(edges, e)
} }
} }
if len(edges) == 0 {
return graph.Empty
}
return iterator.NewOrderedWeightedEdges(edges) return iterator.NewOrderedWeightedEdges(edges)
} }

View File

@@ -59,25 +59,25 @@ func TestWeightedUndirected(t *testing.T) {
testgraph.NodeExistence(t, weightedUndirectedBuilder) testgraph.NodeExistence(t, weightedUndirectedBuilder)
}) })
t.Run("ReturnAdjacentNodes", func(t *testing.T) { t.Run("ReturnAdjacentNodes", func(t *testing.T) {
testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder) testgraph.ReturnAdjacentNodes(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnAllEdges", func(t *testing.T) { t.Run("ReturnAllEdges", func(t *testing.T) {
testgraph.ReturnAllEdges(t, weightedUndirectedBuilder) testgraph.ReturnAllEdges(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnAllNodes", func(t *testing.T) { t.Run("ReturnAllNodes", func(t *testing.T) {
testgraph.ReturnAllNodes(t, weightedUndirectedBuilder) testgraph.ReturnAllNodes(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnAllWeightedEdges", func(t *testing.T) { t.Run("ReturnAllWeightedEdges", func(t *testing.T) {
testgraph.ReturnAllWeightedEdges(t, weightedUndirectedBuilder) testgraph.ReturnAllWeightedEdges(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnEdgeSlice", func(t *testing.T) { t.Run("ReturnEdgeSlice", func(t *testing.T) {
testgraph.ReturnEdgeSlice(t, weightedUndirectedBuilder) testgraph.ReturnEdgeSlice(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) { t.Run("ReturnWeightedEdgeSlice", func(t *testing.T) {
testgraph.ReturnWeightedEdgeSlice(t, weightedUndirectedBuilder) testgraph.ReturnWeightedEdgeSlice(t, weightedUndirectedBuilder, true)
}) })
t.Run("ReturnNodeSlice", func(t *testing.T) { t.Run("ReturnNodeSlice", func(t *testing.T) {
testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder) testgraph.ReturnNodeSlice(t, weightedUndirectedBuilder, true)
}) })
t.Run("Weight", func(t *testing.T) { t.Run("Weight", func(t *testing.T) {
testgraph.Weight(t, weightedUndirectedBuilder) testgraph.Weight(t, weightedUndirectedBuilder)

View File

@@ -25,16 +25,23 @@ import (
// compared with NaN-awareness, so they may be NaN when there is no edge // compared with NaN-awareness, so they may be NaN when there is no edge
// associated with the Weight call. // associated with the Weight call.
// BUG(kortschak): The approach of using a nil return for empty sets of nodes func isValidIterator(it graph.Iterator) bool {
// and edges used prior to the introduction of the graph.Iterator types does return it != nil
// 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 func checkEmptyIterator(t *testing.T, it graph.Iterator, useEmpty bool) {
// outcome of https://github.com/gonum/gonum/issues/614. if it.Len() != 0 {
func isValidIterator(graph.Iterator) bool { return
// TODO(kortschak): Remove nil guards in iterator }
// loops and slicer tests if this changes. if it != graph.Empty {
return true 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 // 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 // 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 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. // 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 { for _, test := range testCases {
g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
checkEmptyIterator(t, it, useEmpty)
var got []graph.Node var got []graph.Node
for it != nil && it.Next() { for it.Next() {
got = append(got, it.Node()) 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 // 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 // interface. This is a check of the Nodes method of graph.Graph and the
// iterator that is returned. // 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 { for _, test := range testCases {
g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
checkEmptyIterator(t, it, useEmpty)
if it == nil { if it == nil {
continue continue
} }
@@ -168,7 +181,9 @@ func NodeExistence(t *testing.T, b Builder) {
// the Edges method of graph.Graph and the iterator that is returned. // the Edges method of graph.Graph and the iterator that is returned.
// ReturnAllEdges also checks that the edge end nodes exist within the graph, // ReturnAllEdges also checks that the edge end nodes exist within the graph,
// checking the Node method of graph.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 { for _, test := range testCases {
g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
for it != nil && it.Next() { checkEmptyIterator(t, it, useEmpty)
for it.Next() {
e := it.Edge() e := it.Edge()
got = append(got, e) got = append(got, e)
if g.Edge(e.From().ID(), e.To().ID()) == nil { 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 // 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 // iterator that is returned. ReturnEdgeSlice also checks that the edge end
// nodes exist within the graph, checking the Node method of graph.Graph. // 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 { for _, test := range testCases {
g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
checkEmptyIterator(t, it, useEmpty)
if it == nil { if it == nil {
continue continue
} }
@@ -267,7 +286,9 @@ func ReturnEdgeSlice(t *testing.T, b Builder) {
// //
// The edges used within and returned by the Builder function should be // 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. // 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 { for _, test := range testCases {
g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
checkEmptyIterator(t, it, useEmpty)
for _, e := range graph.EdgesOf(it) { for _, e := range graph.EdgesOf(it) {
if g.Edge(e.From().ID(), e.To().ID()) == nil { if g.Edge(e.From().ID(), e.To().ID()) == nil {
t.Errorf("missing edge for test %q: %v", test.name, e) 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. // and graph.Edges.
switch lit := e.(type) { switch lit := e.(type) {
case graph.Lines: case graph.Lines:
for lit != nil && lit.Next() { for lit.Next() {
got = append(got, lit.Line()) got = append(got, lit.Line())
} }
case graph.WeightedLines: case graph.WeightedLines:
for lit != nil && lit.Next() { for lit.Next() {
got = append(got, lit.WeightedLine()) got = append(got, lit.WeightedLine())
} }
default: default:
@@ -331,7 +353,9 @@ func ReturnAllLines(t *testing.T, b Builder) {
// The edges used within and returned by the Builder function should be // 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. The edge parameter passed to b will contain only
// graph.WeightedEdge. // 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 { for _, test := range testCases {
g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
for it != nil && it.Next() { checkEmptyIterator(t, it, useEmpty)
for it.Next() {
e := it.WeightedEdge() e := it.WeightedEdge()
got = append(got, e) got = append(got, e)
switch g := g.(type) { 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 // 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. The edge parameter passed to b will contain only
// graph.WeightedEdge. // 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 { for _, test := range testCases {
g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
checkEmptyIterator(t, it, useEmpty)
s, ok := it.(graph.WeightedEdgeSlicer) s, ok := it.(graph.WeightedEdgeSlicer)
if !ok { if !ok {
t.Errorf("invalid type for test %T: cannot return weighted edge slice", g) 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 // 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. The edge parameter passed to b will contain only
// graph.WeightedLine. // 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 { for _, test := range testCases {
g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { 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) t.Errorf("invalid iterator for test %q: got:%#v", test.name, it)
continue continue
} }
checkEmptyIterator(t, it, useEmpty)
for _, e := range graph.WeightedEdgesOf(it) { for _, e := range graph.WeightedEdgesOf(it) {
if g.Edge(e.From().ID(), e.To().ID()) == nil { if g.Edge(e.From().ID(), e.To().ID()) == nil {
t.Errorf("missing edge for test %q: %v", test.name, e) 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. // and graph.Edges.
switch lit := e.(type) { switch lit := e.(type) {
case graph.Lines: case graph.Lines:
for lit != nil && lit.Next() { for lit.Next() {
got = append(got, lit.Line()) got = append(got, lit.Line())
} }
case graph.WeightedLines: case graph.WeightedLines:
for lit != nil && lit.Next() { for lit.Next() {
got = append(got, lit.WeightedLine()) got = append(got, lit.WeightedLine())
} }
default: default:
@@ -602,7 +633,9 @@ func EdgeExistence(t *testing.T, b Builder) {
// within the graph, checking the Node, Edge, EdgeBetween and HasEdgeBetween // within the graph, checking the Node, Edge, EdgeBetween and HasEdgeBetween
// methods of graph.Graph, the EdgeBetween method of graph.Undirected and the // methods of graph.Graph, the EdgeBetween method of graph.Undirected and the
// HasEdgeFromTo method of graph.Directed. // 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 { for _, test := range testCases {
g, nodes, edges, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) g, nodes, edges, _, _, ok := b(test.nodes, test.edges, test.self, test.absent)
if !ok { if !ok {
@@ -620,7 +653,12 @@ func ReturnAdjacentNodes(t *testing.T, b Builder) {
// Test forward. // Test forward.
u := x u := x
it := g.From(u.ID()) 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() v := it.Node()
if i == 0 && g.Node(u.ID()) == nil { if i == 0 && g.Node(u.ID()) == nil {
t.Errorf("missing from node for test %q: %v", test.name, u.ID()) 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. // Test backward.
v := x v := x
it = g.To(v.ID()) 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() u := it.Node()
if i == 0 && g.Node(v.ID()) == nil { if i == 0 && g.Node(v.ID()) == nil {
t.Errorf("missing to node for test %q: %v", test.name, v.ID()) 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: case graph.Undirected:
u := x u := x
it := g.From(u.ID()) 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() v := it.Node()
if i == 0 && g.Node(u.ID()) == nil { if i == 0 && g.Node(u.ID()) == nil {
t.Errorf("missing from node for test %q: %v", test.name, u.ID()) 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: default:
u := x u := x
it := g.From(u.ID()) 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() v := it.Node()
if i == 0 && g.Node(u.ID()) == nil { if i == 0 && g.Node(u.ID()) == nil {
t.Errorf("missing from node for test %q: %v", test.name, u.ID()) t.Errorf("missing from node for test %q: %v", test.name, u.ID())

View File

@@ -252,7 +252,7 @@ func (g johnsonGraph) Nodes() graph.Nodes {
func (g johnsonGraph) From(id int64) graph.Nodes { func (g johnsonGraph) From(id int64) graph.Nodes {
adj := g.succ[id] adj := g.succ[id]
if len(adj) == 0 { if len(adj) == 0 {
return nil return graph.Empty
} }
succ := make([]graph.Node, 0, len(adj)) succ := make([]graph.Node, 0, len(adj))
for id := range adj { for id := range adj {