Skip to content

Commit ea1a59f

Browse files
merge: feat: improving implementation of graphs (#418)
* feat: improving implementation of graphs * Minor refactoring in constructors Remove separate constructors for each type of class. Instead export the attribute `Directed` so that users can customize the `Graph` the way that they want. Similar to the implementation of `DefaultServer` in `net/http` package. Co-authored-by: Taj <[email protected]>
1 parent e192812 commit ea1a59f

File tree

2 files changed

+162
-17
lines changed

2 files changed

+162
-17
lines changed

graph/graph.go

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,54 @@
1-
// This file contains the simple structural implementation of undirected
2-
// graph. Used in coloring algorithms [here](./coloring)
3-
// Author(s): [Shivam](https://github.com/Shivam010)
1+
// This file contains the simple structural implementation of
2+
// directed & undirected graphs used within the graph package
3+
// Author(s): [Shivam](https://github.com/Shivam010), [Tahmeed](https://github.com/Tahmeed156)
44

55
package graph
66

7-
// UndirectedGraph provides a structure to store an undirected graph.
7+
// Graph provides a structure to store the graph.
88
// It is safe to use its empty object.
9-
type UndirectedGraph struct {
9+
type Graph struct {
1010
vertices int
11-
edges map[int]map[int]struct{}
11+
edges map[int]map[int]int // Stores weight of an edge
12+
Directed bool // Differentiate directed/undirected graphs
1213
}
1314

14-
// AddVertex will add a new vertex in the graph, if the vertex already
15-
// exist it will do nothing
16-
func (g *UndirectedGraph) AddVertex(v int) {
15+
// Constructor functions for graphs (undirected by default)
16+
func New(v int) *Graph {
17+
return &Graph{
18+
vertices: v,
19+
}
20+
}
21+
22+
// AddVertex will add a new vertex in the graph.
23+
// If the vertex already exists it will do nothing.
24+
func (g *Graph) AddVertex(v int) {
1725
if g.edges == nil {
18-
g.edges = make(map[int]map[int]struct{})
26+
g.edges = make(map[int]map[int]int)
1927
}
2028

2129
// Check if vertex is present or not
2230
if _, ok := g.edges[v]; !ok {
23-
g.vertices++
24-
g.edges[v] = make(map[int]struct{})
31+
g.edges[v] = make(map[int]int)
2532
}
2633
}
2734

2835
// AddEdge will add a new edge between the provided vertices in the graph
29-
func (g *UndirectedGraph) AddEdge(one, two int) {
36+
func (g *Graph) AddEdge(one, two int) {
37+
// Add an edge with 0 weight
38+
g.AddWeightedEdge(one, two, 0)
39+
}
40+
41+
// AddWeightedEdge will add a new weighted edge between the provided vertices in the graph
42+
func (g *Graph) AddWeightedEdge(one, two, weight int) {
3043
// Add vertices: one and two to the graph if they are not present
3144
g.AddVertex(one)
3245
g.AddVertex(two)
3346

34-
// and finally add the edges: one->two and two->one for undirected graph
35-
g.edges[one][two] = struct{}{}
36-
g.edges[two][one] = struct{}{}
47+
// And finally add the edges
48+
// one->two and two->one for undirected graph
49+
// one->two for directed graphs
50+
g.edges[one][two] = weight
51+
if !g.Directed {
52+
g.edges[two][one] = weight
53+
}
3754
}

graph/graph_test.go

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,131 @@
1-
// Empty test file to keep track of all the tests for the algorithms.
1+
// Tests for directed and undirected graphs
22

33
package graph
4+
5+
import (
6+
"fmt"
7+
"testing"
8+
)
9+
10+
var graphTestCases = []struct {
11+
name string
12+
edges [][]int
13+
vertices int
14+
}{
15+
{
16+
"single edge",
17+
[][]int{
18+
{0, 1, 1},
19+
},
20+
2,
21+
},
22+
{
23+
"many edges",
24+
[][]int{
25+
{0, 1, 1},
26+
{0, 2, 2},
27+
{1, 3, 4},
28+
{3, 4, 3},
29+
{4, 8, 3},
30+
{4, 9, 1},
31+
{7, 8, 2},
32+
{8, 9, 2},
33+
},
34+
10,
35+
},
36+
{
37+
"cycles",
38+
[][]int{
39+
{0, 1, 1},
40+
{0, 2, 2},
41+
{1, 3, 4},
42+
{3, 4, 3},
43+
{4, 2, 1},
44+
},
45+
5,
46+
},
47+
{
48+
"disconnected graphs",
49+
[][]int{
50+
{0, 1, 5},
51+
{2, 4, 5},
52+
{3, 8, 5},
53+
},
54+
2,
55+
},
56+
}
57+
58+
func TestDirectedGraph(t *testing.T) {
59+
60+
// Testing self-loops separately only for directed graphs.
61+
// For undirected graphs each edge already creates a self-loop.
62+
directedGraphTestCases := append(graphTestCases, struct {
63+
name string
64+
edges [][]int
65+
vertices int
66+
}{
67+
"self-loops",
68+
[][]int{
69+
{0, 1, 1},
70+
{1, 2, 2},
71+
{2, 1, 3},
72+
},
73+
3,
74+
})
75+
76+
for _, test := range directedGraphTestCases {
77+
t.Run(fmt.Sprint(test.name), func(t *testing.T) {
78+
// Initializing graph, adding edges
79+
graph := New(test.vertices)
80+
graph.Directed = true
81+
for _, edge := range test.edges {
82+
graph.AddWeightedEdge(edge[0], edge[1], edge[2])
83+
}
84+
85+
if graph.vertices != test.vertices {
86+
t.Errorf("Number of vertices, Expected: %d, Computed: %d", test.vertices, graph.vertices)
87+
}
88+
edgeCount := 0
89+
for _, e := range graph.edges {
90+
edgeCount += len(e)
91+
}
92+
if edgeCount != len(test.edges) {
93+
t.Errorf("Number of edges, Expected: %d, Computed: %d", len(test.edges), edgeCount)
94+
}
95+
for _, edge := range test.edges {
96+
if val, found := graph.edges[edge[0]][edge[1]]; !found || val != edge[2] {
97+
t.Errorf("Edge {%d->%d (%d)} not found", edge[0], edge[1], edge[2])
98+
}
99+
}
100+
})
101+
}
102+
}
103+
104+
func TestUndirectedGraph(t *testing.T) {
105+
106+
for _, test := range graphTestCases {
107+
t.Run(fmt.Sprint(test.name), func(t *testing.T) {
108+
// Initializing graph, adding edges
109+
graph := New(test.vertices)
110+
for _, edge := range test.edges {
111+
graph.AddWeightedEdge(edge[0], edge[1], edge[2])
112+
}
113+
114+
if graph.vertices != test.vertices {
115+
t.Errorf("Number of vertices, Expected: %d, Computed: %d", test.vertices, graph.vertices)
116+
}
117+
edgeCount := 0
118+
for _, e := range graph.edges {
119+
edgeCount += len(e)
120+
}
121+
if edgeCount != len(test.edges)*2 {
122+
t.Errorf("Number of edges, Expected: %d, Computed: %d", len(test.edges)*2, edgeCount)
123+
}
124+
for _, edge := range test.edges {
125+
if val, found := graph.edges[edge[0]][edge[1]]; !found || val != edge[2] {
126+
t.Errorf("Edge {%d->%d (%d)} not found", edge[0], edge[1], edge[2])
127+
}
128+
}
129+
})
130+
}
131+
}

0 commit comments

Comments
 (0)