|
|
|
@@ -0,0 +1,252 @@
|
|
|
|
|
package tests
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"datastructures/graphs"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestNewWeightedGraphIsEmpty(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[int, int]()
|
|
|
|
|
|
|
|
|
|
if graph.Order() != 0 {
|
|
|
|
|
t.Errorf("new graph Order = %d, want 0", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 0 {
|
|
|
|
|
t.Errorf("new graph Size = %d, want 0", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWeightedAddVertexCreatesBothAndConnects(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
|
|
|
|
|
if !graph.AddVertex("a", "b", 4) {
|
|
|
|
|
t.Error("AddVertex on a new edge = false, want true")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if graph.Order() != 2 {
|
|
|
|
|
t.Errorf("Order = %d, want 2", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 1 {
|
|
|
|
|
t.Errorf("Size = %d, want 1", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
adjacency := graph.Display()
|
|
|
|
|
if adjacency["a"]["b"] != 4 {
|
|
|
|
|
t.Errorf("weight a->b = %d, want 4", adjacency["a"]["b"])
|
|
|
|
|
}
|
|
|
|
|
// undirected: the reverse direction carries the same weight
|
|
|
|
|
if adjacency["b"]["a"] != 4 {
|
|
|
|
|
t.Errorf("weight b->a = %d, want 4 (undirected)", adjacency["b"]["a"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adding a second edge from the same vertex must not clobber the first.
|
|
|
|
|
func TestWeightedAddVertexKeepsPreviousEdges(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
graph.AddVertex("a", "b", 4)
|
|
|
|
|
graph.AddVertex("a", "c", 2)
|
|
|
|
|
graph.AddVertex("a", "d", 7)
|
|
|
|
|
|
|
|
|
|
adjacency := graph.Display()
|
|
|
|
|
if len(adjacency["a"]) != 3 {
|
|
|
|
|
t.Errorf("expected vertex a to have degree 3, got %v", adjacency["a"])
|
|
|
|
|
}
|
|
|
|
|
for neighbor, want := range map[string]int{"b": 4, "c": 2, "d": 7} {
|
|
|
|
|
if adjacency["a"][neighbor] != want {
|
|
|
|
|
t.Errorf("weight a->%s = %d, want %d", neighbor, adjacency["a"][neighbor], want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if graph.Order() != 4 {
|
|
|
|
|
t.Errorf("Order = %d, want 4", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 3 {
|
|
|
|
|
t.Errorf("Size = %d, want 3", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWeightedAddVertexRejectsDuplicateEdge(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
graph.AddVertex("a", "b", 4)
|
|
|
|
|
|
|
|
|
|
if graph.AddVertex("a", "b", 99) {
|
|
|
|
|
t.Error("AddVertex on an existing edge = true, want false")
|
|
|
|
|
}
|
|
|
|
|
// the original weight is kept, not overwritten
|
|
|
|
|
if weight := graph.Display()["a"]["b"]; weight != 4 {
|
|
|
|
|
t.Errorf("weight a->b = %d, want 4 (existing weight kept)", weight)
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 1 {
|
|
|
|
|
t.Errorf("Size = %d, want 1", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWeightedSelfLoopIsRejected(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[int, int]()
|
|
|
|
|
|
|
|
|
|
if graph.AddVertex(1, 1, 5) {
|
|
|
|
|
t.Error("AddVertex with a self-loop = true, want false")
|
|
|
|
|
}
|
|
|
|
|
if graph.Order() != 0 {
|
|
|
|
|
t.Errorf("self-loop should create no vertex, Order = %d", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 0 {
|
|
|
|
|
t.Errorf("self-loop should add no edge, Size = %d", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// a real edge on the same vertex must still work
|
|
|
|
|
graph.AddVertex(1, 2, 5)
|
|
|
|
|
if graph.Size() != 1 {
|
|
|
|
|
t.Errorf("Size = %d, want 1 after a valid edge", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWeightedAddIsolatedVertex(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
|
|
|
|
|
if !graph.AddIsolatedVertex("solo") {
|
|
|
|
|
t.Error("AddIsolatedVertex on new vertex = false, want true")
|
|
|
|
|
}
|
|
|
|
|
if graph.AddIsolatedVertex("solo") {
|
|
|
|
|
t.Error("AddIsolatedVertex on existing vertex = true, want false")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if graph.Order() != 1 {
|
|
|
|
|
t.Errorf("Order = %d, want 1", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 0 {
|
|
|
|
|
t.Errorf("isolated vertex should add no edges, Size = %d", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
if len(graph.Display()["solo"]) != 0 {
|
|
|
|
|
t.Errorf("isolated vertex should have empty adjacency, got %v", graph.Display()["solo"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// An existing vertex must not be reset when a later edge is added to it.
|
|
|
|
|
func TestWeightedAddVertexPreservesExistingVertex(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
graph.AddVertex("a", "b", 4)
|
|
|
|
|
graph.AddVertex("c", "a", 2)
|
|
|
|
|
|
|
|
|
|
adjacency := graph.Display()
|
|
|
|
|
if len(adjacency["a"]) != 2 {
|
|
|
|
|
t.Errorf("expected vertex a to keep both edges, got %v", adjacency["a"])
|
|
|
|
|
}
|
|
|
|
|
if adjacency["a"]["b"] != 4 {
|
|
|
|
|
t.Errorf("weight a->b = %d, want 4", adjacency["a"]["b"])
|
|
|
|
|
}
|
|
|
|
|
if adjacency["a"]["c"] != 2 {
|
|
|
|
|
t.Errorf("weight a->c = %d, want 2", adjacency["a"]["c"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConnectEdgeAllExisting(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[int, int]()
|
|
|
|
|
graph.AddIsolatedVertex(1)
|
|
|
|
|
graph.AddIsolatedVertex(2)
|
|
|
|
|
|
|
|
|
|
if !graph.ConnectEdge(1, 2, 8) {
|
|
|
|
|
t.Error("ConnectEdge with existing vertices = false, want true")
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 1 {
|
|
|
|
|
t.Errorf("Size = %d, want 1", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
adjacency := graph.Display()
|
|
|
|
|
if adjacency[1][2] != 8 {
|
|
|
|
|
t.Errorf("weight 1->2 = %d, want 8", adjacency[1][2])
|
|
|
|
|
}
|
|
|
|
|
if adjacency[2][1] != 8 {
|
|
|
|
|
t.Errorf("weight 2->1 = %d, want 8 (undirected)", adjacency[2][1])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConnectEdgeRejectsMissingVertex(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[int, int]()
|
|
|
|
|
graph.AddIsolatedVertex(1)
|
|
|
|
|
// 99 does not exist
|
|
|
|
|
|
|
|
|
|
if graph.ConnectEdge(1, 99, 3) {
|
|
|
|
|
t.Error("ConnectEdge with a missing target = true, want false")
|
|
|
|
|
}
|
|
|
|
|
if graph.ConnectEdge(99, 1, 3) {
|
|
|
|
|
t.Error("ConnectEdge with a missing source = true, want false")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if graph.Order() != 1 {
|
|
|
|
|
t.Errorf("ConnectEdge should not create vertices, Order = %d, want 1", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 0 {
|
|
|
|
|
t.Errorf("no edges should be added on failure, Size = %d", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConnectEdgeRejectsDuplicate(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[int, int]()
|
|
|
|
|
graph.AddIsolatedVertex(1)
|
|
|
|
|
graph.AddIsolatedVertex(2)
|
|
|
|
|
graph.ConnectEdge(1, 2, 8)
|
|
|
|
|
|
|
|
|
|
if graph.ConnectEdge(1, 2, 99) {
|
|
|
|
|
t.Error("ConnectEdge on an existing edge = true, want false")
|
|
|
|
|
}
|
|
|
|
|
if weight := graph.Display()[1][2]; weight != 8 {
|
|
|
|
|
t.Errorf("weight 1->2 = %d, want 8 (existing weight kept)", weight)
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 1 {
|
|
|
|
|
t.Errorf("Size = %d, want 1", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWeightedOrderAndSize(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
graph.AddVertex("a", "b", 4)
|
|
|
|
|
graph.AddVertex("b", "c", 7)
|
|
|
|
|
graph.AddVertex("c", "a", 2)
|
|
|
|
|
|
|
|
|
|
if graph.Order() != 3 {
|
|
|
|
|
t.Errorf("Order = %d, want 3 (triangle)", graph.Order())
|
|
|
|
|
}
|
|
|
|
|
if graph.Size() != 3 {
|
|
|
|
|
t.Errorf("Size = %d, want 3 (triangle)", graph.Size())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A missing edge reads as the zero weight, so existence needs the comma-ok form.
|
|
|
|
|
func TestWeightedMissingEdgeIsZero(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, int]()
|
|
|
|
|
graph.AddVertex("a", "b", 4)
|
|
|
|
|
|
|
|
|
|
weight, exists := graph.Display()["a"]["z"]
|
|
|
|
|
if exists {
|
|
|
|
|
t.Error("lookup of a missing edge reported exists = true, want false")
|
|
|
|
|
}
|
|
|
|
|
if weight != 0 {
|
|
|
|
|
t.Errorf("missing edge weight = %d, want the zero value 0", weight)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWeightedFloatWeights(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, float64]()
|
|
|
|
|
graph.AddVertex("a", "b", 2.5)
|
|
|
|
|
|
|
|
|
|
if weight := graph.Display()["a"]["b"]; weight != 2.5 {
|
|
|
|
|
t.Errorf("weight a->b = %v, want 2.5", weight)
|
|
|
|
|
}
|
|
|
|
|
if weight := graph.Display()["b"]["a"]; weight != 2.5 {
|
|
|
|
|
t.Errorf("weight b->a = %v, want 2.5 (undirected)", weight)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A defined type with a numeric underlying type satisfies the Number constraint.
|
|
|
|
|
type Distance float64
|
|
|
|
|
|
|
|
|
|
func TestWeightedNamedWeightType(t *testing.T) {
|
|
|
|
|
graph := graphs.NewWeightedGraph[string, Distance]()
|
|
|
|
|
graph.AddVertex("a", "b", Distance(1.5))
|
|
|
|
|
|
|
|
|
|
if weight := graph.Display()["a"]["b"]; weight != Distance(1.5) {
|
|
|
|
|
t.Errorf("weight a->b = %v, want 1.5", weight)
|
|
|
|
|
}
|
|
|
|
|
}
|