new file: graphs/weightedGraph.go
Go / build (push) Successful in 34s

This commit is contained in:
Acid
2026-07-24 03:43:24 -04:00
parent 666c1a155b
commit 5fb97bc757
3 changed files with 343 additions and 1 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
## Graph ## Graph
- [x] Unweighted - [x] Unweighted
- [ ] Weighted - [x] Weighted
- [ ] Directed - [ ] Directed
## Sets ## Sets
+90
View File
@@ -0,0 +1,90 @@
package graphs
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
// Weighted Graph , undirected
type WeightedGraph[T comparable, W Number] struct {
adjList map[T]map[T]W
}
// NewWeightedGraph() -> Creates a Weighted Graph.
//
// gg := graphs.NewWeightedGraph[string, int]()
func NewWeightedGraph[T comparable, W Number]() *WeightedGraph[T, W] {
gp := &WeightedGraph[T, W]{
adjList: make(map[T]map[T]W),
}
return gp
}
// AddVertex() -> add vertex connected to vertex with weight
func (gp *WeightedGraph[T, W]) AddVertex(newVertex T, connectToVertex T, weight W) bool {
// early return
if newVertex == connectToVertex {
return false
}
gp.AddIsolatedVertex(newVertex)
gp.AddIsolatedVertex(connectToVertex)
_, exist := gp.adjList[newVertex][connectToVertex]
if !exist {
gp.adjList[newVertex][connectToVertex] = weight
gp.adjList[connectToVertex][newVertex] = weight
return true
}
return false
}
// AddIsolatedVertex() -> Creates an isolated Vertex returns True if ok ,
// false if vertex already exist
func (gp *WeightedGraph[T, W]) AddIsolatedVertex(newVertex T) bool {
_, exist := gp.adjList[newVertex]
if !exist {
gp.adjList[newVertex] = make(map[T]W)
return true
}
return false
}
// ConnectEdge() -> Connects two already existing vertices with a weighted edge.
// Returns false if either vertex is missing or the edge is already there.
func (gp *WeightedGraph[T, W]) ConnectEdge(vertex T, to T, weight W) bool {
if _, exist := gp.adjList[vertex]; !exist {
return false
}
if _, exist := gp.adjList[to]; !exist {
return false
}
return gp.AddVertex(vertex, to, weight)
}
// Display()
func (gp *WeightedGraph[T, W]) Display() map[T]map[T]W {
return gp.adjList
}
// Order() -> returns the amount of Vertices
func (gp *WeightedGraph[T, W]) Order() int {
return len(gp.adjList)
}
// Size() -> returns the amount of Edges
func (gp *WeightedGraph[T, W]) Size() int {
var size int
for _, inner := range gp.adjList {
size += len(inner)
}
return size / 2
}
+252
View File
@@ -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)
}
}