Added undirected graph management

main
oabrivard 2 years ago
parent 9b5259c192
commit 726d2b923f

11
.gitignore vendored

@ -1,5 +1,16 @@
# Mac OSX
.DS_Store .DS_Store
# IntelliJ
*.iml
*.ipr
*.iws
.idea/
/build/
/dist/
/out/
# VS Code
settings.json settings.json
# ---> Go # ---> Go

@ -1,8 +1,8 @@
package algo package algo
type Subset struct { type Subset[T comparable] struct {
parent string Parent T
rank int Rank int
} }
// find is a recursive function that finds the root of the subset that element 'e' belongs to. // 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. // The function returns the root of the subset that 'e' belongs to.
// see https://ondrej-kvasnovsky-2.gitbook.io/algorithms/finding/union-find-algorithms // see https://ondrej-kvasnovsky-2.gitbook.io/algorithms/finding/union-find-algorithms
// or https://jojozhuang.github.io/algorithm/algorithm-union-find/ // 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 // find root and make root as parent of e
// (path compression) // (path compression)
if subsets[e].parent != e { if subsets[e].Parent != e {
subsets[e].parent = find(subsets, subsets[e].parent) 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. // 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. // 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. // 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) { func Union[T comparable](subsets map[T]*Subset[T], x, y T) {
xroot := find(subsets, x) xroot := Find(subsets, x)
yroot := find(subsets, y) yroot := Find(subsets, y)
// Attach smaller rank tree under root of high // Attach smaller rank tree under root of high
// rank tree (Union by Rank) // rank tree (Union by Rank)
if subsets[xroot].rank < subsets[yroot].rank { if subsets[xroot].Rank < subsets[yroot].Rank {
subsets[xroot].parent = yroot subsets[xroot].Parent = yroot
} else { } else {
if subsets[xroot].rank > subsets[yroot].rank { if subsets[xroot].Rank > subsets[yroot].Rank {
subsets[yroot].parent = xroot subsets[yroot].Parent = xroot
} else { } else {
// If ranks are same, then make one as root and // If ranks are same, then make one as root and
// increment its rank by one // increment its rank by one
subsets[yroot].parent = xroot subsets[yroot].Parent = xroot
subsets[xroot].rank++ subsets[xroot].Rank++
} }
} }
} }

@ -5,16 +5,16 @@ import (
) )
func TestFind(t *testing.T) { func TestFind(t *testing.T) {
subsets := map[string]*Subset{ subsets := map[string]*Subset[string]{
"a": {parent: "a"}, "a": {Parent: "a"},
"b": {parent: "a"}, "b": {Parent: "a"},
"c": {parent: "b"}, "c": {Parent: "b"},
"d": {parent: "c"}, "d": {Parent: "c"},
} }
tests := []struct { tests := []struct {
name string name string
subsets map[string]*Subset subsets map[string]*Subset[string]
element string element string
expected string expected string
}{ }{
@ -46,7 +46,7 @@ func TestFind(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got := find(tt.subsets, tt.element) got := Find(tt.subsets, tt.element)
if got != tt.expected { if got != tt.expected {
t.Errorf("find() = %v, want %v", 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) { func TestUnion(t *testing.T) {
subsets := map[string]*Subset{ subsets := map[string]*Subset[string]{
"a": {parent: "a"}, "a": {Parent: "a"},
"b": {parent: "a"}, "b": {Parent: "a"},
"c": {parent: "b"}, "c": {Parent: "b"},
"d": {parent: "c"}, "d": {Parent: "c"},
} }
tests := []struct { tests := []struct {
name string name string
subsets map[string]*Subset subsets map[string]*Subset[string]
x string x string
y string y string
expectedXRoot string expectedXRoot string
@ -102,7 +102,7 @@ func TestUnion(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
Union(tt.subsets, tt.x, tt.y) Union(tt.subsets, tt.x, tt.y)
got := find(tt.subsets, tt.x) got := Find(tt.subsets, tt.x)
if got != tt.expectedXRoot { if got != tt.expectedXRoot {
t.Errorf("Union() failed, got x root = %v, want %v", got, tt.expectedXRoot) t.Errorf("Union() failed, got x root = %v, want %v", got, tt.expectedXRoot)
} }

@ -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{}
}

@ -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)
}
}
Loading…
Cancel
Save