diff --git a/.gitignore b/.gitignore index 117bc10..a09fef2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,16 @@ +# Mac OSX .DS_Store +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +/build/ +/dist/ +/out/ + +# VS Code settings.json # ---> Go diff --git a/algo/unionfind.go b/algo/unionfind.go index 6fb5359..62f71a1 100644 --- a/algo/unionfind.go +++ b/algo/unionfind.go @@ -1,8 +1,8 @@ package algo -type Subset struct { - parent string - rank int +type Subset[T comparable] struct { + Parent T + Rank int } // find is a recursive function that finds the root of the subset that element 'e' belongs to. @@ -12,34 +12,34 @@ type Subset struct { // The function returns the root of the subset that 'e' belongs to. // see https://ondrej-kvasnovsky-2.gitbook.io/algorithms/finding/union-find-algorithms // or https://jojozhuang.github.io/algorithm/algorithm-union-find/ -func find(subsets map[string]*Subset, e string) string { +func Find[T comparable](subsets map[T]*Subset[T], e T) T { // find root and make root as parent of e // (path compression) - if subsets[e].parent != e { - subsets[e].parent = find(subsets, subsets[e].parent) + if subsets[e].Parent != e { + subsets[e].Parent = Find(subsets, subsets[e].Parent) } - return subsets[e].parent + return subsets[e].Parent } // Union merges two subsets in the union-find data structure. // It takes a map of subsets, and the names of the two subsets to be merged. // The function finds the root of each subset and performs the union operation based on the rank of the subsets. -func Union(subsets map[string]*Subset, x, y string) { - xroot := find(subsets, x) - yroot := find(subsets, y) +func Union[T comparable](subsets map[T]*Subset[T], x, y T) { + xroot := Find(subsets, x) + yroot := Find(subsets, y) // Attach smaller rank tree under root of high // rank tree (Union by Rank) - if subsets[xroot].rank < subsets[yroot].rank { - subsets[xroot].parent = yroot + if subsets[xroot].Rank < subsets[yroot].Rank { + subsets[xroot].Parent = yroot } else { - if subsets[xroot].rank > subsets[yroot].rank { - subsets[yroot].parent = xroot + if subsets[xroot].Rank > subsets[yroot].Rank { + subsets[yroot].Parent = xroot } else { // If ranks are same, then make one as root and // increment its rank by one - subsets[yroot].parent = xroot - subsets[xroot].rank++ + subsets[yroot].Parent = xroot + subsets[xroot].Rank++ } } } diff --git a/algo/unionfind_test.go b/algo/unionfind_test.go index 6c26442..469fdbc 100644 --- a/algo/unionfind_test.go +++ b/algo/unionfind_test.go @@ -5,16 +5,16 @@ import ( ) func TestFind(t *testing.T) { - subsets := map[string]*Subset{ - "a": {parent: "a"}, - "b": {parent: "a"}, - "c": {parent: "b"}, - "d": {parent: "c"}, + subsets := map[string]*Subset[string]{ + "a": {Parent: "a"}, + "b": {Parent: "a"}, + "c": {Parent: "b"}, + "d": {Parent: "c"}, } tests := []struct { name string - subsets map[string]*Subset + subsets map[string]*Subset[string] element string expected string }{ @@ -46,7 +46,7 @@ func TestFind(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := find(tt.subsets, tt.element) + got := Find(tt.subsets, tt.element) if got != tt.expected { t.Errorf("find() = %v, want %v", got, tt.expected) } @@ -55,16 +55,16 @@ func TestFind(t *testing.T) { } func TestUnion(t *testing.T) { - subsets := map[string]*Subset{ - "a": {parent: "a"}, - "b": {parent: "a"}, - "c": {parent: "b"}, - "d": {parent: "c"}, + subsets := map[string]*Subset[string]{ + "a": {Parent: "a"}, + "b": {Parent: "a"}, + "c": {Parent: "b"}, + "d": {Parent: "c"}, } tests := []struct { name string - subsets map[string]*Subset + subsets map[string]*Subset[string] x string y string expectedXRoot string @@ -102,7 +102,7 @@ func TestUnion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { Union(tt.subsets, tt.x, tt.y) - got := find(tt.subsets, tt.x) + got := Find(tt.subsets, tt.x) if got != tt.expectedXRoot { t.Errorf("Union() failed, got x root = %v, want %v", got, tt.expectedXRoot) } diff --git a/graph/graph.go b/graph/graph.go new file mode 100644 index 0000000..ff972de --- /dev/null +++ b/graph/graph.go @@ -0,0 +1,512 @@ +// Package graph is an undirected graph management library. +// Do not support multithreaded updates. +package graph + +import ( + "fmt" + "math" + "math/rand" + "slices" + "strings" + + "gitea.paas.celticinfo.fr/oabrivard/abrolgo/algo" + "gitea.paas.celticinfo.fr/oabrivard/abrolgo/container" + "golang.org/x/exp/constraints" +) + +type Edge[T comparable, V constraints.Integer] struct { + ID int + src T + dest T + weight V +} + +type Graph[T comparable, V constraints.Integer] struct { + edgeID int + v, e int // number of edges and vertices + adjacencyList map[T][]Edge[T, V] + edges []Edge[T, V] +} + +// NewGraph creates a new instance of the undirected Graph data structure. +// It initializes the graph with zero vertices and zero edges. +// The graph uses a map to store the adjacency list and a slice to store the edges. +// The type parameters T and V represent the types of the vertices and edge values, respectively. +// The vertices must be comparable, and the edge values must be integers. +// Returns a pointer to the newly created Graph. +func NewGraph[T comparable, V constraints.Integer]() *Graph[T, V] { + return &Graph[T, V]{ + edgeID: 0, + v: 0, + e: 0, + adjacencyList: map[T][]Edge[T, V]{}, + edges: []Edge[T, V]{}, + } +} + +// AddVertex adds a new vertex to the graph. +// This function initializes an empty adjacency list for the new vertex. +func (g *Graph[T, V]) AddVertex(vertex T) { + g.adjacencyList[vertex] = []Edge[T, V]{} +} + +// AddEdge adds an edge to the graph between the source node (src) and the destination node (dest), +// with the given distance (dist). +// It adds the edge in both directions (src -> dest and dest -> src). +// If vertices src and dest do not exist in the graph, they are added to the graph. +func (g *Graph[T, V]) AddEdge(src, dest T, dist V) { + g.edgeID++ + srcToDest := Edge[T, V]{g.edgeID, src, dest, dist} + destToSrc := Edge[T, V]{g.edgeID, dest, src, dist} + g.adjacencyList[src] = append(g.adjacencyList[src], srcToDest) + g.adjacencyList[dest] = append(g.adjacencyList[dest], destToSrc) + g.edges = append(g.edges, srcToDest) + g.e++ + g.v = len(g.adjacencyList) +} + +// RemoveEdge removes the first edge between the source node (src) and the destination node (dest). +// It updates the adjacency list, the list of edges, and the number of edges in the graph. +// If the edge does not exist, it does nothing. +func (g *Graph[T, V]) RemoveEdge(src, dest T) { + var ID int + edgeFound := false + + // Check if source vertex exists + if _, exists := g.adjacencyList[src]; !exists { + // Optionally handle the case where the source vertex does not exist + return + } + + // Remove the edge from the adjacency list + for i, edge := range g.adjacencyList[src] { + if edge.dest == dest { + g.adjacencyList[src] = append(g.adjacencyList[src][:i], g.adjacencyList[src][i+1:]...) + ID = edge.ID + edgeFound = true + break + } + } + + // Remove the reciprocal edge + for i, edge := range g.adjacencyList[dest] { + if edge.ID == ID { + g.adjacencyList[dest] = append(g.adjacencyList[dest][:i], g.adjacencyList[dest][i+1:]...) + break + } + } + + // If no edge was removed from the adjacency list, return to avoid the uneceesary loop + if !edgeFound { + return + } + + // Remove the edge from the list of edges + for i, edge := range g.edges { + if edge.ID == ID { + g.edges = append(g.edges[:i], g.edges[i+1:]...) + break + } + } + + g.e-- +} + +// RemoveEdge removes the first edge between the source node (src) and the destination node (dest). +// It updates the adjacency list, the list of edges, and the number of edges in the graph. +// If the edge does not exist, it does nothing. +func (g *Graph[T, V]) RemoveEdgeWithID(ID int) { + edgeFound := false + var edgeToRemove Edge[T, V] + + // Remove the edge from the list of edges + for i, edge := range g.edges { + if edge.ID == ID { + edgeFound = true + edgeToRemove = edge + g.edges = append(g.edges[:i], g.edges[i+1:]...) + break + } + } + + // Return if no edge found + if !edgeFound { + return + } + + src := edgeToRemove.src + dest := edgeToRemove.dest + + // Remove the edge from the adjacency list + for i, edge := range g.adjacencyList[src] { + if edge.ID == ID { + g.adjacencyList[src] = append(g.adjacencyList[src][:i], g.adjacencyList[src][i+1:]...) + break + } + } + + // Remove the reciprocal edge + for i, edge := range g.adjacencyList[dest] { + if edge.ID == ID { + g.adjacencyList[dest] = append(g.adjacencyList[dest][:i], g.adjacencyList[dest][i+1:]...) + break + } + } + + g.e-- +} + +// RemoveVertex removes the specified vertex from the graph. +// It updates the adjacency list, the list of edges, and the number of edges and vertices in the graph. +// If the vertex does not exist, it does nothing. +func (g *Graph[T, V]) RemoveVertex(vertex T) { + // Remove the vertex from the adjacency list + delete(g.adjacencyList, vertex) + + // Remove all edges in the adjacency list that have 'vertex' as their destination + for src, edges := range g.adjacencyList { + for i := 0; i < len(edges); { + if edges[i].dest == vertex { + // Remove edge from the slice + edges = append(edges[:i], edges[i+1:]...) + g.e-- // Decrement the edge count + } else { + i++ + } + } + g.adjacencyList[src] = edges + } + + // Remove all edges from the edges slice that involve 'vertex' + for i := 0; i < len(g.edges); { + if g.edges[i].src == vertex || g.edges[i].dest == vertex { + // Remove edge from the slice + g.edges = append(g.edges[:i], g.edges[i+1:]...) + } else { + i++ + } + } + + g.v = len(g.adjacencyList) +} + +// GetVertices returns the list of vertices in the graph. +func (g *Graph[T, V]) GetVertices() []T { + vertices := make([]T, len(g.adjacencyList)) + i := 0 + for vertex := range g.adjacencyList { + vertices[i] = vertex + i++ + } + return vertices +} + +// GetEdges returns the list of edges in the graph. +func (g *Graph[T, V]) GetEdges() []Edge[T, V] { + return g.edges +} + +// GetNeighbors returns the list of edges connected to the specified vertex in the graph. +func (g *Graph[T, V]) GetNeighbors(vertex T) []Edge[T, V] { + return g.adjacencyList[vertex] +} + +// HasNeighbor checks if a given source node has a neighbor node in the graph. +func (g *Graph[T, V]) HasNeighbor(src, neighbor T) bool { + return slices.ContainsFunc(g.adjacencyList[src], func(edge Edge[T, V]) bool { + return edge.dest == neighbor + }) +} + +// GetEdge returns the first edge between the source node (src) and the destination node (dest). +// If the edge does not exist, it returns an empty Edge. +func (g *Graph[T, V]) GetEdge(src, dest T) Edge[T, V] { + for _, edge := range g.adjacencyList[src] { + if edge.dest == dest { + return edge + } + } + return Edge[T, V]{} +} + +// String returns a string representation of the graph. +func (g *Graph[T, V]) String() string { + var sb strings.Builder + sb.Grow(512) // Provide an initial capacity estimate + + for vertex, edges := range g.adjacencyList { + sb.WriteString(fmt.Sprint(vertex)) + sb.WriteString(": ") + + edgeStrings := make([]string, len(edges)) + for i, e := range edges { + edgeStrings[i] = fmt.Sprintf("(%v, %v, %v, %v)", e.ID, e.src, e.dest, e.weight) + } + + sb.WriteString(strings.Join(edgeStrings, " ")) + sb.WriteString("\n") + } + + return sb.String() +} + +// CountComponents returns the number of connected components in the graph. +func (g *Graph[T, V]) CountComponents() int { + visited := map[T]bool{} + count := 0 + + // We perform a breadth-first search starting from each unvisited vertex + // and increment the count for each new component found. + for vertex := range g.adjacencyList { + if !visited[vertex] { + count++ + queue := container.NewQueue[T]() + queue.Enqueue(vertex) + + for queue.HasElement() { + curVert := queue.Dequeue() + visited[curVert] = true + + for _, linkedNode := range g.adjacencyList[curVert] { + if !visited[linkedNode.dest] { + queue.Enqueue(linkedNode.dest) + } + } + } + } + } + return count +} + +// IsConnected returns true if the graph is connected, false otherwise. +func (g *Graph[T, V]) IsConnected() bool { + return g.CountComponents() == 1 +} + +// IsCyclic returns true if the graph contains a cycle, false otherwise. +func (g *Graph[T, V]) IsCyclic() bool { + visited := map[T]bool{} + recStack := map[T]bool{} + edgeIDs := map[int]bool{} + + for vertex := range g.adjacencyList { + if g.isCyclicUtil(vertex, visited, recStack, edgeIDs) { + return true + } + } + return false +} + +// isCyclicUtil is a helper function for IsCyclic. +// It performs a depth-first search on the graph starting from the specified vertex. +// It returns true if the graph contains a cycle, false otherwise. +func (g *Graph[T, V]) isCyclicUtil(vertex T, visited, recStack map[T]bool, edgeIDs map[int]bool) bool { + visited[vertex] = true + recStack[vertex] = true + + for _, neighbor := range g.adjacencyList[vertex] { + // If the edge has already been visited via a reciprocal link, skip it + if edgeIDs[neighbor.ID] { + continue + } + edgeIDs[neighbor.ID] = true + + if !visited[neighbor.dest] && g.isCyclicUtil(neighbor.dest, visited, recStack, edgeIDs) { + return true + } else if recStack[neighbor.dest] { + return true + } + } + recStack[vertex] = false + return false +} + +// IsTree returns true if the graph is a tree, false otherwise. +func (g *Graph[T, V]) IsTree() bool { + return g.IsConnected() && !g.IsCyclic() +} + +// IsBipartite returns true if the graph is bipartite, false otherwise. +func (g *Graph[T, V]) IsBipartite() bool { + visited := map[T]bool{} + colors := map[T]bool{} + edgeIDs := map[int]bool{} + + for vertex := range g.adjacencyList { + if !visited[vertex] { + if !g.isBipartiteUtil(vertex, visited, colors, edgeIDs) { + return false + } + } + } + return true +} + +// isBipartiteUtil is a helper function for IsBipartite. +// It performs a depth-first search on the graph starting from the specified vertex. +// It returns true if the graph is bipartite, false otherwise. +func (g *Graph[T, V]) isBipartiteUtil(vertex T, visited, colors map[T]bool, edgeIDs map[int]bool) bool { + visited[vertex] = true + + for _, neighbor := range g.adjacencyList[vertex] { + // If the edge has already been visited via a reciprocal link, skip it + if edgeIDs[neighbor.ID] { + continue + } + edgeIDs[neighbor.ID] = true + + if !visited[neighbor.dest] { + colors[neighbor.dest] = !colors[vertex] + if !g.isBipartiteUtil(neighbor.dest, visited, colors, edgeIDs) { + return false + } + } else if colors[vertex] == colors[neighbor.dest] { + return false + } + } + return true +} + +// kargerMinCutUF performs the Karger's algorithm to find the minimum cut in the graph using the Union-Find data structure. +// It returns the number of cut edges and the subsets of vertices after the contraction. +// Being a monte-carlo algorithm, it can return a wrong result with a very small probability. +// Adapted from https://www.geeksforgeeks.org/introduction-and-implementation-of-kargers-algorithm-for-minimum-cut/?ref=lbp +func (g *Graph[T, V]) kargerMinCutUF(loops int) (int, [][]T) { + var subsets map[T]*algo.Subset[T] + minCut := math.MaxInt64 + + for loops > 0 { + loops-- + + // Allocate memory for creating V subsets. + subsets = map[T]*algo.Subset[T]{} + + // Get data of given graph + edges := g.edges + + // Create V subsets with single elements + for k := range g.adjacencyList { + subsets[k] = &algo.Subset[T]{Parent: k, Rank: 0} + } + + // Initially there are V vertices in + // contracted graph + vertices := g.v + + // Keep contracting vertices until there are + // 2 vertices. + for vertices > 2 { + // Pick a random edge + i := rand.Int() % g.e + + // Find vertices (or sets) of two corners + // of current edge + subset1 := algo.Find(subsets, edges[i].src) + subset2 := algo.Find(subsets, edges[i].dest) + + // If two corners belong to same subset, + // then no point considering this edge + if subset1 == subset2 { + continue + } else { + // Else contract the edge (or combine the + // corners of edge into one vertex) + vertices-- + algo.Union(subsets, subset1, subset2) + } + } + + // Now we have two vertices (or subsets) left in + // the contracted graph, so count the edges between + // two components and return the count. + cutedges := 0 + for i := 0; i < g.e; i++ { + subset1 := algo.Find(subsets, edges[i].src) + subset2 := algo.Find(subsets, edges[i].dest) + if subset1 != subset2 { + cutedges++ + } + } + + if cutedges < minCut { + minCut = cutedges + } + } + + // Consolidate the subsets + consolidatedSubsets := map[T][]T{} + for k, v := range subsets { + _, found := consolidatedSubsets[v.Parent] + if !found { + consolidatedSubsets[v.Parent] = []T{} + } + consolidatedSubsets[v.Parent] = append(consolidatedSubsets[v.Parent], k) + } + + resultSubsets := [][]T{} + for _, s := range consolidatedSubsets { + resultSubsets = append(resultSubsets, s) + } + + return minCut, resultSubsets +} + +// DFS performs a depth-first search starting from the specified vertex until a goal vertex is reached. +// It returns a slice of vertices visited during the search. +// If no goal vertex is found, an empty slice is returned. +func (g *Graph[T, V]) DFS(start T, isGoal func(vertex T) bool) []T { + visited := map[T]bool{} + result := []T{} + stack := container.NewStack[T]() + + stack.Push(start) + + for stack.HasElement() { + curVert := stack.Pop() + result = append(result, curVert) + + if isGoal(curVert) { + return result + } + + if !visited[curVert] { + visited[curVert] = true + + for _, linkedNode := range g.adjacencyList[curVert] { + stack.Push(linkedNode.dest) + } + } + } + + return []T{} +} + +// BFS performs a breadth-first search starting from the specified vertex until a goal vertex is reached. +// It returns a slice of vertices visited during the search. +// If no goal vertex is found, an empty slice is returned. +func (g *Graph[T, V]) BFS(start T, isGoal func(vertex T) bool) []T { + visited := map[T]bool{} + result := []T{} + queue := container.NewQueue[T]() + + visited[start] = true + queue.Enqueue(start) + + for queue.HasElement() { + curVert := queue.Dequeue() + result = append(result, curVert) + + if isGoal(curVert) { + return result + } + + for _, linkedNode := range g.adjacencyList[curVert] { + if !visited[linkedNode.dest] { + visited[linkedNode.dest] = true + queue.Enqueue(linkedNode.dest) + } + } + } + + return []T{} +} diff --git a/graph/graph_test.go b/graph/graph_test.go new file mode 100644 index 0000000..13c06e7 --- /dev/null +++ b/graph/graph_test.go @@ -0,0 +1,724 @@ +package graph + +import ( + "sort" + "strings" + "testing" +) + +func TestNewGraph(t *testing.T) { + g := NewGraph[int, int]() + + // Check if the graph is initialized correctly + if g.v != 0 { + t.Errorf("NewGraph() failed: expected v = 0, got v = %d", g.v) + } + if g.e != 0 { + t.Errorf("NewGraph() failed: expected e = 0, got e = %d", g.e) + } + if len(g.adjacencyList) != 0 { + t.Errorf("NewGraph() failed: expected empty adjacency list, got %v", g.adjacencyList) + } + if len(g.edges) != 0 { + t.Errorf("NewGraph() failed: expected empty edges list, got %v", g.edges) + } +} + +func TestAddEdge(t *testing.T) { + g := NewGraph[int, int]() + + // Add edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Check if the edges are added correctly + expectedV := 4 + if g.v != expectedV { + t.Errorf("AddEdge() failed: expected v = %d, got v = %d", expectedV, g.v) + } + + expectedE := 3 + if g.e != expectedE { + t.Errorf("AddEdge() failed: expected e = %d, got e = %d", expectedE, g.e) + } + + expectedAdjacencyList := map[int][]Edge[int, int]{ + 1: {{1, 1, 2, 10}}, + 2: {{1, 2, 1, 10}, {2, 2, 3, 20}}, + 3: {{2, 3, 2, 20}, {3, 3, 4, 30}}, + 4: {{3, 4, 3, 30}}, + } + if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) { + t.Errorf("AddEdge() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList) + } + + expectedEdges := []Edge[int, int]{ + {1, 1, 2, 10}, + {2, 2, 3, 20}, + {3, 3, 4, 30}, + } + if !compareEdges(g.edges, expectedEdges) { + t.Errorf("AddEdge() failed: expected edges list = %v, got %v", expectedEdges, g.edges) + } +} + +func compareAdjacencyLists(a, b map[int][]Edge[int, int]) bool { + if len(a) != len(b) { + return false + } + + for key, valueA := range a { + valueB, ok := b[key] + if !ok || !compareEdges(valueA, valueB) { + return false + } + } + + return true +} + +func compareEdges(a, b []Edge[int, int]) bool { + if len(a) != len(b) { + return false + } + + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + + return true +} + +func TestGetNeighbors(t *testing.T) { + // Create a new graph + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(2, 3, 30) + + // Test GetNeighbors for vertex 1 + neighbors := g.GetNeighbors(1) + expectedNeighbors := []Edge[int, int]{{ID: 1, src: 1, dest: 2, weight: 10}, {ID: 2, src: 1, dest: 3, weight: 20}} + if !equalEdges(neighbors, expectedNeighbors) { + t.Errorf("GetNeighbors() failed for vertex 1: expected %v, got %v", expectedNeighbors, neighbors) + } + + // Test GetNeighbors for vertex 2 + neighbors = g.GetNeighbors(2) + expectedNeighbors = []Edge[int, int]{{ID: 1, src: 2, dest: 1, weight: 10}, {ID: 3, src: 2, dest: 3, weight: 30}} + if !equalEdges(neighbors, expectedNeighbors) { + t.Errorf("GetNeighbors() failed for vertex 2: expected %v, got %v", expectedNeighbors, neighbors) + } + + // Test GetNeighbors for vertex 3 + neighbors = g.GetNeighbors(3) + expectedNeighbors = []Edge[int, int]{{ID: 2, src: 3, dest: 1, weight: 20}, {ID: 3, src: 3, dest: 2, weight: 30}} + if !equalEdges(neighbors, expectedNeighbors) { + t.Errorf("GetNeighbors() failed for vertex 3: expected %v, got %v", expectedNeighbors, neighbors) + } +} + +// Helper function to check if two slices of edges are equal +func equalEdges(edges1, edges2 []Edge[int, int]) bool { + if len(edges1) != len(edges2) { + return false + } + for i := 0; i < len(edges1); i++ { + if edges1[i] != edges2[i] { + return false + } + } + return true +} + +func TestHasNeighbor(t *testing.T) { + g := NewGraph[int, int]() + + // Add edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(2, 3, 30) + + // Test HasNeighbor for existing neighbors + if !g.HasNeighbor(1, 2) { + t.Errorf("HasNeighbor() failed: expected true, got false") + } + if !g.HasNeighbor(1, 3) { + t.Errorf("HasNeighbor() failed: expected true, got false") + } + if !g.HasNeighbor(2, 3) { + t.Errorf("HasNeighbor() failed: expected true, got false") + } + if !g.HasNeighbor(3, 1) { + t.Errorf("HasNeighbor() failed: expected true, got false") + } + if !g.HasNeighbor(2, 1) { + t.Errorf("HasNeighbor() failed: expected true, got false") + } + + // Test HasNeighbor for non-existing neighbors + if g.HasNeighbor(1, 4) { + t.Errorf("HasNeighbor() failed: expected false, got true") + } +} + +func TestGetEdge(t *testing.T) { + g := NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Test existing edge + expectedEdge := Edge[int, int]{ID: 1, src: 1, dest: 2, weight: 10} + edge := g.GetEdge(1, 2) + if edge != expectedEdge { + t.Errorf("GetEdge() failed for existing edge: expected %v, got %v", expectedEdge, edge) + } + + // Test non-existing edge + edge = g.GetEdge(1, 3) + if edge != (Edge[int, int]{}) { + t.Errorf("GetEdge() failed for non-existing edge: expected empty edge, got %v", edge) + } +} + +func TestString(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(2, 3, 30) + + result := g.String() + + if !strings.Contains(result, "1: (1, 1, 2, 10) (2, 1, 3, 20)\n") || !strings.Contains(result, "2: (1, 2, 1, 10) (3, 2, 3, 30)\n") || !strings.Contains(result, "3: (2, 3, 1, 20) (3, 3, 2, 30)\n") { + t.Errorf("String() failed and got %q", result) + } +} + +func TestCountComponents(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(4, 5, 30) + + expectedCount := 2 + actualCount := g.CountComponents() + if actualCount != expectedCount { + t.Errorf("CountComponents() failed: expected count = %d, got count = %d", expectedCount, actualCount) + } + + // Add another component to the graph + g.AddEdge(6, 7, 40) + g.AddEdge(7, 8, 50) + + expectedCount = 3 + actualCount = g.CountComponents() + if actualCount != expectedCount { + t.Errorf("CountComponents() failed: expected count = %d, got count = %d", expectedCount, actualCount) + } +} + +func TestIsConnected(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Test IsConnected for a connected graph + if !g.IsConnected() { + t.Errorf("IsConnected() failed: expected true, got false") + } + + // Test IsConnected for a disconnected graph + g.AddVertex(5) + if g.IsConnected() { + t.Errorf("IsConnected() failed: expected false, got true") + } +} + +func TestIsCyclic(t *testing.T) { + g := NewGraph[int, int]() + + // Add edges to create a cyclic graph + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 1, 30) + + // Check if the graph is cyclic + if !g.IsCyclic() { + t.Errorf("IsCyclic() failed: expected true, got false") + } + + // Add edges to create an acyclic graph + g = NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Check if the graph is acyclic + if g.IsCyclic() { + t.Errorf("IsCyclic() failed: expected false, got true") + } +} + +func TestIsCyclicUtil(t *testing.T) { + g := NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 1, 30) + + visited := make(map[int]bool) + recStack := make(map[int]bool) + edgeIDs := make(map[int]bool) + + // Test for cyclic graph + if !g.isCyclicUtil(1, visited, recStack, edgeIDs) { + t.Errorf("isCyclicUtil() failed: expected true, got false") + } + + g = NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + visited = make(map[int]bool) + recStack = make(map[int]bool) + edgeIDs = make(map[int]bool) + + // Test for acyclic graph + if g.isCyclicUtil(1, visited, recStack, edgeIDs) { + t.Errorf("isCyclicUtil() failed: expected false, got true") + } +} + +func TestIsTree(t *testing.T) { + // Create a new graph + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Test IsTree for a connected acyclic graph + if !g.IsTree() { + t.Errorf("IsTree() failed for a connected acyclic graph") + } + + // Add an edge to create a cycle + g.AddEdge(4, 1, 40) + + // Test IsTree for a graph with a cycle + if g.IsTree() { + t.Errorf("IsTree() failed for a graph with a cycle") + } + + // Remove the edge to make the graph acyclic again + g.RemoveEdge(4, 1) + + // Remove a vertex to make the graph disconnected + g.RemoveVertex(3) + + // Test IsTree for a disconnected graph + if g.IsTree() { + t.Errorf("IsTree() failed for a disconnected graph") + } +} + +func TestIsBipartite(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(3, 4, 40) + + // Test for a bipartite graph + if !g.IsBipartite() { + t.Error("IsBipartite() failed for a bipartite graph") + } + + // Add an edge to create a cycle + g.AddEdge(4, 1, 50) + + // Test for a non-bipartite graph with a cycle + if g.IsBipartite() { + t.Error("IsBipartite() failed for a non-bipartite graph with a cycle") + } + + // Remove the edge to break the cycle + g.RemoveEdge(4, 1) + + // Test for a non-bipartite graph without a cycle + if !g.IsBipartite() { + t.Error("IsBipartite() failed for a non-bipartite graph without a cycle") + } +} + +func TestIsBipartiteUtil(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + visited := make(map[int]bool) + colors := make(map[int]bool) + edgeIDs := map[int]bool{} + + // Test isBipartiteUtil for a bipartite graph + result := g.isBipartiteUtil(1, visited, colors, edgeIDs) + expectedResult := true + if result != expectedResult { + t.Errorf("isBipartiteUtil() failed for bipartite graph: expected %v, got %v", expectedResult, result) + } + + visited = make(map[int]bool) + colors = make(map[int]bool) + edgeIDs = map[int]bool{} + + // Test isBipartiteUtil for a non-bipartite graph + g.AddEdge(4, 2, 40) + result = g.isBipartiteUtil(1, visited, colors, edgeIDs) + expectedResult = false + if result != expectedResult { + t.Errorf("isBipartiteUtil() failed for non-bipartite graph: expected %v, got %v", expectedResult, result) + } +} + +func TestAddVertex(t *testing.T) { + g := NewGraph[int, int]() + + // Add a vertex to the graph + g.AddVertex(1) + + // Check if the vertex is added correctly + expectedAdjacencyList := map[int][]Edge[int, int]{ + 1: {}, + } + if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) { + t.Errorf("AddVertex() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList) + } +} +func TestGetVertices(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices to the graph + g.AddVertex(1) + g.AddVertex(2) + g.AddVertex(3) + + // Get the vertices from the graph + vertices := g.GetVertices() + + // Check if the vertices are returned correctly + expectedVertices := []int{1, 2, 3} + if !equalSlices(vertices, expectedVertices) { + t.Errorf("GetVertices() failed: expected %v, got %v", expectedVertices, vertices) + } +} + +// Helper function to check if two slices are equal +func equalSlices(a, b []int) bool { + sort.Ints(a) + sort.Ints(b) + + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestRemoveEdge(t *testing.T) { + g := NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Test removing an existing edge + g.RemoveEdge(1, 2) + if edges := g.GetEdges(); len(edges) != 2 { + t.Errorf("RemoveEdge() failed: expected 2 edges, got %d", len(edges)) + } + if neighbors := g.GetNeighbors(1); len(neighbors) != 0 { + t.Errorf("RemoveEdge() failed: expected 0 neighbors for vertex 1, got %d", len(neighbors)) + } + if neighbors := g.GetNeighbors(2); len(neighbors) != 1 { + t.Errorf("RemoveEdge() failed: expected 1 neighbor for vertex 2, got %d", len(neighbors)) + } + + // Test removing a non-existing edge + g.RemoveEdge(1, 3) + if edges := g.GetEdges(); len(edges) != 2 { + t.Errorf("RemoveEdge() failed: expected 2 edges, got %d", len(edges)) + } + if neighbors := g.GetNeighbors(1); len(neighbors) != 0 { + t.Errorf("RemoveEdge() failed: expected 0 neighbors for vertex 1, got %d", len(neighbors)) + } + if neighbors := g.GetNeighbors(3); len(neighbors) != 2 { + t.Errorf("RemoveEdge() failed: expected 1 neighbor for vertex 3, got %d", len(neighbors)) + } +} + +func TestRemoveVertex(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(2, 3, 30) + g.AddEdge(3, 4, 40) + + // Remove vertex 2 + g.RemoveVertex(2) + + // Check if the vertex is removed correctly + expectedAdjacencyList := map[int][]Edge[int, int]{ + 1: {{2, 1, 3, 20}}, + 3: {{2, 3, 1, 20}, {4, 3, 4, 40}}, + 4: {{4, 4, 3, 40}}, + } + if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) { + t.Errorf("RemoveVertex() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList) + } + + expectedEdges := []Edge[int, int]{ + {2, 1, 3, 20}, + {4, 3, 4, 40}, + } + if !compareEdges(g.edges, expectedEdges) { + t.Errorf("RemoveVertex() failed: expected edges list = %v, got %v", expectedEdges, g.edges) + } + + expectedV := 3 + if g.v != expectedV { + t.Errorf("RemoveVertex() failed: expected v = %d, got v = %d", expectedV, g.v) + } + + expectedE := 2 + if g.e != expectedE { + t.Errorf("RemoveVertex() failed: expected e = %d, got e = %d", expectedE, g.e) + } +} + +func TestGetEdges(t *testing.T) { + g := NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Test GetEdges + expectedEdges := []Edge[int, int]{ + {ID: 1, src: 1, dest: 2, weight: 10}, + {ID: 2, src: 2, dest: 3, weight: 20}, + {ID: 3, src: 3, dest: 4, weight: 30}, + } + edges := g.GetEdges() + if !compareEdges(edges, expectedEdges) { + t.Errorf("GetEdges() failed: expected edges = %v, got edges = %v", expectedEdges, edges) + } +} + +func TestRemoveEdgeWithID(t *testing.T) { + g := NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + + // Test removing an existing edge + g.RemoveEdgeWithID(1) + + // Check if the edge is removed from the list of edges + expectedEdges := []Edge[int, int]{ + {ID: 2, src: 2, dest: 3, weight: 20}, + {ID: 3, src: 3, dest: 4, weight: 30}, + } + if !compareEdges(g.edges, expectedEdges) { + t.Errorf("RemoveEdgeWithID() failed: expected edges list = %v, got %v", expectedEdges, g.edges) + } + + // Check if the edge is removed from the adjacency list + expectedAdjacencyList := map[int][]Edge[int, int]{ + 1: {}, + 2: {{2, 2, 3, 20}}, + 3: {{2, 3, 2, 20}, {3, 3, 4, 30}}, + 4: {{3, 4, 3, 30}}, + } + if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) { + t.Errorf("RemoveEdgeWithID() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList) + } + + // Check if the edge count is updated + expectedE := 2 + if g.e != expectedE { + t.Errorf("RemoveEdgeWithID() failed: expected e = %d, got e = %d", expectedE, g.e) + } + + // Test removing a non-existing edge + g.RemoveEdgeWithID(5) + + // Check if the graph remains unchanged + if !compareEdges(g.edges, expectedEdges) { + t.Errorf("RemoveEdgeWithID() failed: expected edges list = %v, got %v", expectedEdges, g.edges) + } + if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) { + t.Errorf("RemoveEdgeWithID() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList) + } + if g.e != expectedE { + t.Errorf("RemoveEdgeWithID() failed: expected e = %d, got e = %d", expectedE, g.e) + } +} +func TestKargerMinCutUF(t *testing.T) { + // Create a new graph + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(2, 3, 30) + g.AddEdge(3, 4, 40) + g.AddEdge(4, 5, 50) + g.AddEdge(4, 6, 60) + g.AddEdge(5, 6, 70) + + // Run Karger's algorithm to find the minimum cut + cutEdges, resultSubsets := g.kargerMinCutUF(10000) + + // Check if the number of cut edges is correct + expectedCutEdges := 1 + if cutEdges != expectedCutEdges { + t.Errorf("kargerMinCutUF() failed: expected cut edges = %d, got cut edges = %d", expectedCutEdges, cutEdges) + } + + // Check if the result subsets are correct + expectedResultSubsets := [][]int{{1, 2, 3}, {4, 5, 6}} + if !equalSubsets(resultSubsets, expectedResultSubsets) { + t.Errorf("kargerMinCutUF() failed: expected result subsets = %v, got result subsets = %v", expectedResultSubsets, resultSubsets) + } +} + +// Helper function to check if two subsets are equal +func equalSubsets(a, b [][]int) bool { + if len(a) != len(b) { + return false + } + + for i := 0; i < len(a); i++ { + if !equalSlices(a[i], b[i]) { + return false + } + } + + return true +} + +func TestBFS(t *testing.T) { + g := NewGraph[int, int]() + + // Add vertices and edges to the graph + g.AddEdge(1, 2, 10) + g.AddEdge(1, 3, 20) + g.AddEdge(2, 3, 30) + g.AddEdge(2, 4, 40) + g.AddEdge(3, 4, 50) + + // Define the isGoal function + isGoal := func(vertex int) bool { + return vertex == 4 + } + + // Perform BFS starting from vertex 1 + result := g.BFS(1, isGoal) + + // Check if the result is correct + expectedResult := []int{1, 2, 3, 4} + if !equalSlices(result, expectedResult) { + t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result) + } + + // Perform BFS starting from vertex 2 + result = g.BFS(2, isGoal) + + // Check if the result is correct + expectedResult = []int{2, 1, 3, 4} + if !equalSlices(result, expectedResult) { + t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result) + } + + // Perform BFS starting from vertex 3 + result = g.BFS(3, isGoal) + + // Check if the result is correct + expectedResult = []int{3, 1, 2, 4} + if !equalSlices(result, expectedResult) { + t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result) + } + + // Perform BFS starting from vertex 4 + result = g.BFS(4, isGoal) + + // Check if the result is correct + expectedResult = []int{4} + if !equalSlices(result, expectedResult) { + t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result) + } +} + +func TestDFS(t *testing.T) { + g := NewGraph[int, int]() + g.AddEdge(1, 2, 10) + g.AddEdge(2, 3, 20) + g.AddEdge(3, 4, 30) + g.AddEdge(4, 5, 40) + g.AddEdge(5, 6, 50) + + // Test DFS with a goal function that returns true for vertex 6 + isGoal := func(vertex int) bool { + return vertex == 6 + } + + result := g.DFS(1, isGoal) + expectedResult := []int{1, 2, 3, 4, 5, 6} + if !equalSlices(result, expectedResult) { + t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result) + } + + // Test DFS with a goal function that returns true for vertex 3 + isGoal = func(vertex int) bool { + return vertex == 3 + } + + result = g.DFS(1, isGoal) + expectedResult = []int{1, 2, 3} + if !equalSlices(result, expectedResult) { + t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result) + } + + // Test DFS with a goal function that returns true for vertex 7 (non-existent vertex) + isGoal = func(vertex int) bool { + return vertex == 7 + } + + result = g.DFS(1, isGoal) + expectedResult = []int{} + if !equalSlices(result, expectedResult) { + t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result) + } +}