Skip to content

Commit 4d80f04

Browse files
authored
Merge branch 'master' into master
2 parents a5be634 + 495cff8 commit 4d80f04

File tree

16 files changed

+894
-105
lines changed

16 files changed

+894
-105
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
229229

230230
1. [`Base64Decode`](./conversion/base64.go#L57): Base64Decode decodes the received input base64 string into a byte slice. The implementation follows the RFC4648 standard, which is documented at https://datatracker.ietf.org/doc/html/rfc4648#section-4
231231
2. [`Base64Encode`](./conversion/base64.go#L19): Base64Encode encodes the received input bytes slice into a base64 string. The implementation follows the RFC4648 standard, which is documented at https://datatracker.ietf.org/doc/html/rfc4648#section-4
232-
3. [`BinaryToDecimal`](./conversion/binarytodecimal.go#L25): BinaryToDecimal() function that will take Binary number as string, and return it's Decimal equivalent as integer.
233-
4. [`DecimalToBinary`](./conversion/decimaltobinary.go#L32): DecimalToBinary() function that will take Decimal number as int, and return it's Binary equivalent as string.
232+
3. [`BinaryToDecimal`](./conversion/binarytodecimal.go#L25): BinaryToDecimal() function that will take Binary number as string, and return its Decimal equivalent as integer.
233+
4. [`DecimalToBinary`](./conversion/decimaltobinary.go#L32): DecimalToBinary() function that will take Decimal number as int, and return its Binary equivalent as string.
234234
5. [`FuzzBase64Encode`](./conversion/base64_test.go#L113): No description provided.
235235
6. [`HEXToRGB`](./conversion/rgbhex.go#L10): HEXToRGB splits an RGB input (e.g. a color in hex format; 0x<color-code>) into the individual components: red, green and blue
236236
7. [`IntToRoman`](./conversion/inttoroman.go#L17): IntToRoman converts an integer value to a roman numeral string. An error is returned if the integer is not between 1 and 3999.

conversion/binarytodecimal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
var isValid = regexp.MustCompile("^[0-1]{1,}$").MatchString
2222

2323
// BinaryToDecimal() function that will take Binary number as string,
24-
// and return it's Decimal equivalent as integer.
24+
// and return its Decimal equivalent as an integer.
2525
func BinaryToDecimal(binary string) (int, error) {
2626
if !isValid(binary) {
2727
return -1, errors.New("not a valid binary string")

conversion/decimaltobinary.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func Reverse(str string) string {
2828
}
2929

3030
// DecimalToBinary() function that will take Decimal number as int,
31-
// and return it's Binary equivalent as string.
31+
// and return its Binary equivalent as a string.
3232
func DecimalToBinary(num int) (string, error) {
3333
if num < 0 {
3434
return "", errors.New("integer must have +ve value")

graph/kahn.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Kahn's algorithm computes a topological ordering of a directed acyclic graph (DAG).
2+
// Time Complexity: O(V + E)
3+
// Space Complexity: O(V + E)
4+
// Reference: https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
5+
// see graph.go, topological.go, kahn_test.go
6+
7+
package graph
8+
9+
// Kahn's algorithm computes a topological ordering of a directed acyclic graph (DAG).
10+
// `n` is the number of vertices,
11+
// `dependencies` is a list of directed edges, where each pair [a, b] represents
12+
// a directed edge from a to b (i.e. b depends on a).
13+
// Vertices are assumed to be labelled 0, 1, ..., n-1.
14+
// If the graph is not a DAG, the function returns nil.
15+
func Kahn(n int, dependencies [][]int) []int {
16+
g := Graph{vertices: n, Directed: true}
17+
// track the in-degree (number of incoming edges) of each vertex
18+
inDegree := make([]int, n)
19+
20+
// populate g with edges, increase the in-degree counts accordingly
21+
for _, d := range dependencies {
22+
// make sure we don't add the same edge twice
23+
if _, ok := g.edges[d[0]][d[1]]; !ok {
24+
g.AddEdge(d[0], d[1])
25+
inDegree[d[1]]++
26+
}
27+
}
28+
29+
// queue holds all vertices with in-degree 0
30+
// these vertices have no dependency and thus can be ordered first
31+
queue := make([]int, 0, n)
32+
33+
for i := 0; i < n; i++ {
34+
if inDegree[i] == 0 {
35+
queue = append(queue, i)
36+
}
37+
}
38+
39+
// order holds a valid topological order
40+
order := make([]int, 0, n)
41+
42+
// process the dependency-free vertices
43+
// every time we process a vertex, we "remove" it from the graph
44+
for len(queue) > 0 {
45+
// pop the first vertex from the queue
46+
vtx := queue[0]
47+
queue = queue[1:]
48+
// add the vertex to the topological order
49+
order = append(order, vtx)
50+
// "remove" all the edges coming out of this vertex
51+
// every time we remove an edge, the corresponding in-degree reduces by 1
52+
// if all dependencies on a vertex is removed, enqueue the vertex
53+
for neighbour := range g.edges[vtx] {
54+
inDegree[neighbour]--
55+
if inDegree[neighbour] == 0 {
56+
queue = append(queue, neighbour)
57+
}
58+
}
59+
}
60+
61+
// if the graph is a DAG, order should contain all the certices
62+
if len(order) != n {
63+
return nil
64+
}
65+
return order
66+
}

graph/kahn_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package graph
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestKahn(t *testing.T) {
8+
testCases := []struct {
9+
name string
10+
n int
11+
dependencies [][]int
12+
wantNil bool
13+
}{
14+
{
15+
"linear graph",
16+
3,
17+
[][]int{{0, 1}, {1, 2}},
18+
false,
19+
},
20+
{
21+
"diamond graph",
22+
4,
23+
[][]int{{0, 1}, {0, 2}, {1, 3}, {2, 3}},
24+
false,
25+
},
26+
{
27+
"star graph",
28+
5,
29+
[][]int{{0, 1}, {0, 2}, {0, 3}, {0, 4}},
30+
false,
31+
},
32+
{
33+
"disconnected graph",
34+
5,
35+
[][]int{{0, 1}, {0, 2}, {3, 4}},
36+
false,
37+
},
38+
{
39+
"cycle graph 1",
40+
4,
41+
[][]int{{0, 1}, {1, 2}, {2, 3}, {3, 0}},
42+
true,
43+
},
44+
{
45+
"cycle graph 2",
46+
4,
47+
[][]int{{0, 1}, {1, 2}, {2, 0}, {2, 3}},
48+
true,
49+
},
50+
{
51+
"single node graph",
52+
1,
53+
[][]int{},
54+
false,
55+
},
56+
{
57+
"empty graph",
58+
0,
59+
[][]int{},
60+
false,
61+
},
62+
{
63+
"redundant dependencies",
64+
4,
65+
[][]int{{0, 1}, {1, 2}, {1, 2}, {2, 3}},
66+
false,
67+
},
68+
{
69+
"island vertex",
70+
4,
71+
[][]int{{0, 1}, {0, 2}},
72+
false,
73+
},
74+
{
75+
"more complicated graph",
76+
14,
77+
[][]int{{1, 9}, {2, 0}, {3, 2}, {4, 5}, {4, 6}, {4, 7}, {6, 7},
78+
{7, 8}, {9, 4}, {10, 0}, {10, 1}, {10, 12}, {11, 13},
79+
{12, 0}, {12, 11}, {13, 5}},
80+
false,
81+
},
82+
}
83+
84+
for _, tc := range testCases {
85+
t.Run(tc.name, func(t *testing.T) {
86+
actual := Kahn(tc.n, tc.dependencies)
87+
if tc.wantNil {
88+
if actual != nil {
89+
t.Errorf("Kahn(%d, %v) = %v; want nil", tc.n, tc.dependencies, actual)
90+
}
91+
} else {
92+
if actual == nil {
93+
t.Errorf("Kahn(%d, %v) = nil; want valid order", tc.n, tc.dependencies)
94+
} else {
95+
seen := make([]bool, tc.n)
96+
positions := make([]int, tc.n)
97+
for i, v := range actual {
98+
seen[v] = true
99+
positions[v] = i
100+
}
101+
for i, v := range seen {
102+
if !v {
103+
t.Errorf("missing vertex %v", i)
104+
}
105+
}
106+
for _, d := range tc.dependencies {
107+
if positions[d[0]] > positions[d[1]] {
108+
t.Errorf("dependency %v not satisfied", d)
109+
}
110+
}
111+
}
112+
}
113+
})
114+
}
115+
}

graph/kruskal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func KruskalMST(n int, edges []Edge) ([]Edge, int) {
4242
// Add the weight of the edge to the total cost
4343
cost += edge.Weight
4444
// Merge the sets containing the start and end vertices of the current edge
45-
u = u.Union(int(edge.Start), int(edge.End))
45+
u.Union(int(edge.Start), int(edge.End))
4646
}
4747
}
4848

graph/unionfind.go

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
// is used to efficiently maintain connected components in a graph that undergoes dynamic changes,
44
// such as edges being added or removed over time
55
// Worst Case Time Complexity: The time complexity of find operation is nearly constant or
6-
//O(α(n)), where where α(n) is the inverse Ackermann function
6+
//O(α(n)), where α(n) is the inverse Ackermann function
77
// practically, this is a very slowly growing function making the time complexity for find
88
//operation nearly constant.
99
// The time complexity of the union operation is also nearly constant or O(α(n))
1010
// Worst Case Space Complexity: O(n), where n is the number of nodes or element in the structure
1111
// Reference: https://www.scaler.com/topics/data-structures/disjoint-set/
12+
// https://en.wikipedia.org/wiki/Disjoint-set_data_structure
1213
// Author: Mugdha Behere[https://github.com/MugdhaBehere]
1314
// see: unionfind.go, unionfind_test.go
1415

@@ -17,43 +18,45 @@ package graph
1718
// Defining the union-find data structure
1819
type UnionFind struct {
1920
parent []int
20-
size []int
21+
rank []int
2122
}
2223

2324
// Initialise a new union find data structure with s nodes
2425
func NewUnionFind(s int) UnionFind {
2526
parent := make([]int, s)
26-
size := make([]int, s)
27-
for k := 0; k < s; k++ {
28-
parent[k] = k
29-
size[k] = 1
27+
rank := make([]int, s)
28+
for i := 0; i < s; i++ {
29+
parent[i] = i
30+
rank[i] = 1
3031
}
31-
return UnionFind{parent, size}
32+
return UnionFind{parent, rank}
3233
}
3334

34-
// to find the root of the set to which the given element belongs, the Find function serves the purpose
35-
func (u UnionFind) Find(q int) int {
36-
for q != u.parent[q] {
37-
q = u.parent[q]
35+
// Find finds the root of the set to which the given element belongs.
36+
// It performs path compression to make future Find operations faster.
37+
func (u *UnionFind) Find(q int) int {
38+
if q != u.parent[q] {
39+
u.parent[q] = u.Find(u.parent[q])
3840
}
39-
return q
41+
return u.parent[q]
4042
}
4143

42-
// to merge two sets to which the given elements belong, the Union function serves the purpose
43-
func (u UnionFind) Union(a, b int) UnionFind {
44-
rootP := u.Find(a)
45-
rootQ := u.Find(b)
44+
// Union merges the sets, if not already merged, to which the given elements belong.
45+
// It performs union by rank to keep the tree as flat as possible.
46+
func (u *UnionFind) Union(p, q int) {
47+
rootP := u.Find(p)
48+
rootQ := u.Find(q)
4649

4750
if rootP == rootQ {
48-
return u
51+
return
4952
}
5053

51-
if u.size[rootP] < u.size[rootQ] {
54+
if u.rank[rootP] < u.rank[rootQ] {
5255
u.parent[rootP] = rootQ
53-
u.size[rootQ] += u.size[rootP]
56+
} else if u.rank[rootP] > u.rank[rootQ] {
57+
u.parent[rootQ] = rootP
5458
} else {
5559
u.parent[rootQ] = rootP
56-
u.size[rootP] += u.size[rootQ]
60+
u.rank[rootP]++
5761
}
58-
return u
5962
}

graph/unionfind_test.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ func TestUnionFind(t *testing.T) {
88
u := NewUnionFind(10) // Creating a Union-Find data structure with 10 elements
99

1010
//union operations
11-
u = u.Union(0, 1)
12-
u = u.Union(2, 3)
13-
u = u.Union(4, 5)
14-
u = u.Union(6, 7)
11+
u.Union(0, 1)
12+
u.Union(2, 3)
13+
u.Union(4, 5)
14+
u.Union(6, 7)
1515

1616
// Testing the parent of specific elements
1717
t.Run("Test Find", func(t *testing.T) {
@@ -20,12 +20,21 @@ func TestUnionFind(t *testing.T) {
2020
}
2121
})
2222

23-
u = u.Union(1, 5) // Additional union operation
24-
u = u.Union(3, 7) // Additional union operation
23+
u.Union(1, 5) // Additional union operation
24+
u.Union(3, 7) // Additional union operation
2525

2626
// Testing the parent of specific elements after more union operations
2727
t.Run("Test Find after Union", func(t *testing.T) {
28-
if u.Find(0) != u.Find(5) || u.Find(2) != u.Find(7) {
28+
if u.Find(0) != u.Find(5) || u.Find(1) != u.Find(4) || u.Find(2) != u.Find(7) || u.Find(3) != u.Find(6) {
29+
t.Error("Union operation not functioning correctly")
30+
}
31+
})
32+
33+
u.Union(3, 7) // Repeated union operation
34+
35+
// Testing that repeated union operations are idempotent
36+
t.Run("Test Find after repeated Union", func(t *testing.T) {
37+
if u.Find(2) != u.Find(6) || u.Find(2) != u.Find(7) || u.Find(3) != u.Find(6) || u.Find(3) != u.Find(7) {
2938
t.Error("Union operation not functioning correctly")
3039
}
3140
})

0 commit comments

Comments
 (0)