Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaniel27 authored Sep 14, 2024
2 parents a5be634 + 495cff8 commit 4d80f04
Show file tree
Hide file tree
Showing 16 changed files with 894 additions and 105 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.

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
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
3. [`BinaryToDecimal`](./conversion/binarytodecimal.go#L25): BinaryToDecimal() function that will take Binary number as string, and return it's Decimal equivalent as integer.
4. [`DecimalToBinary`](./conversion/decimaltobinary.go#L32): DecimalToBinary() function that will take Decimal number as int, and return it's Binary equivalent as string.
3. [`BinaryToDecimal`](./conversion/binarytodecimal.go#L25): BinaryToDecimal() function that will take Binary number as string, and return its Decimal equivalent as integer.
4. [`DecimalToBinary`](./conversion/decimaltobinary.go#L32): DecimalToBinary() function that will take Decimal number as int, and return its Binary equivalent as string.
5. [`FuzzBase64Encode`](./conversion/base64_test.go#L113): No description provided.
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
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.
Expand Down
2 changes: 1 addition & 1 deletion conversion/binarytodecimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
var isValid = regexp.MustCompile("^[0-1]{1,}$").MatchString

// BinaryToDecimal() function that will take Binary number as string,
// and return it's Decimal equivalent as integer.
// and return its Decimal equivalent as an integer.
func BinaryToDecimal(binary string) (int, error) {
if !isValid(binary) {
return -1, errors.New("not a valid binary string")
Expand Down
2 changes: 1 addition & 1 deletion conversion/decimaltobinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func Reverse(str string) string {
}

// DecimalToBinary() function that will take Decimal number as int,
// and return it's Binary equivalent as string.
// and return its Binary equivalent as a string.
func DecimalToBinary(num int) (string, error) {
if num < 0 {
return "", errors.New("integer must have +ve value")
Expand Down
66 changes: 66 additions & 0 deletions graph/kahn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Kahn's algorithm computes a topological ordering of a directed acyclic graph (DAG).
// Time Complexity: O(V + E)
// Space Complexity: O(V + E)
// Reference: https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
// see graph.go, topological.go, kahn_test.go

package graph

// Kahn's algorithm computes a topological ordering of a directed acyclic graph (DAG).
// `n` is the number of vertices,
// `dependencies` is a list of directed edges, where each pair [a, b] represents
// a directed edge from a to b (i.e. b depends on a).
// Vertices are assumed to be labelled 0, 1, ..., n-1.
// If the graph is not a DAG, the function returns nil.
func Kahn(n int, dependencies [][]int) []int {
g := Graph{vertices: n, Directed: true}
// track the in-degree (number of incoming edges) of each vertex
inDegree := make([]int, n)

// populate g with edges, increase the in-degree counts accordingly
for _, d := range dependencies {
// make sure we don't add the same edge twice
if _, ok := g.edges[d[0]][d[1]]; !ok {
g.AddEdge(d[0], d[1])
inDegree[d[1]]++
}
}

// queue holds all vertices with in-degree 0
// these vertices have no dependency and thus can be ordered first
queue := make([]int, 0, n)

for i := 0; i < n; i++ {
if inDegree[i] == 0 {
queue = append(queue, i)
}
}

// order holds a valid topological order
order := make([]int, 0, n)

// process the dependency-free vertices
// every time we process a vertex, we "remove" it from the graph
for len(queue) > 0 {
// pop the first vertex from the queue
vtx := queue[0]
queue = queue[1:]
// add the vertex to the topological order
order = append(order, vtx)
// "remove" all the edges coming out of this vertex
// every time we remove an edge, the corresponding in-degree reduces by 1
// if all dependencies on a vertex is removed, enqueue the vertex
for neighbour := range g.edges[vtx] {
inDegree[neighbour]--
if inDegree[neighbour] == 0 {
queue = append(queue, neighbour)
}
}
}

// if the graph is a DAG, order should contain all the certices
if len(order) != n {
return nil
}
return order
}
115 changes: 115 additions & 0 deletions graph/kahn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package graph

import (
"testing"
)

func TestKahn(t *testing.T) {
testCases := []struct {
name string
n int
dependencies [][]int
wantNil bool
}{
{
"linear graph",
3,
[][]int{{0, 1}, {1, 2}},
false,
},
{
"diamond graph",
4,
[][]int{{0, 1}, {0, 2}, {1, 3}, {2, 3}},
false,
},
{
"star graph",
5,
[][]int{{0, 1}, {0, 2}, {0, 3}, {0, 4}},
false,
},
{
"disconnected graph",
5,
[][]int{{0, 1}, {0, 2}, {3, 4}},
false,
},
{
"cycle graph 1",
4,
[][]int{{0, 1}, {1, 2}, {2, 3}, {3, 0}},
true,
},
{
"cycle graph 2",
4,
[][]int{{0, 1}, {1, 2}, {2, 0}, {2, 3}},
true,
},
{
"single node graph",
1,
[][]int{},
false,
},
{
"empty graph",
0,
[][]int{},
false,
},
{
"redundant dependencies",
4,
[][]int{{0, 1}, {1, 2}, {1, 2}, {2, 3}},
false,
},
{
"island vertex",
4,
[][]int{{0, 1}, {0, 2}},
false,
},
{
"more complicated graph",
14,
[][]int{{1, 9}, {2, 0}, {3, 2}, {4, 5}, {4, 6}, {4, 7}, {6, 7},
{7, 8}, {9, 4}, {10, 0}, {10, 1}, {10, 12}, {11, 13},
{12, 0}, {12, 11}, {13, 5}},
false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := Kahn(tc.n, tc.dependencies)
if tc.wantNil {
if actual != nil {
t.Errorf("Kahn(%d, %v) = %v; want nil", tc.n, tc.dependencies, actual)
}
} else {
if actual == nil {
t.Errorf("Kahn(%d, %v) = nil; want valid order", tc.n, tc.dependencies)
} else {
seen := make([]bool, tc.n)
positions := make([]int, tc.n)
for i, v := range actual {
seen[v] = true
positions[v] = i
}
for i, v := range seen {
if !v {
t.Errorf("missing vertex %v", i)
}
}
for _, d := range tc.dependencies {
if positions[d[0]] > positions[d[1]] {
t.Errorf("dependency %v not satisfied", d)
}
}
}
}
})
}
}
2 changes: 1 addition & 1 deletion graph/kruskal.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func KruskalMST(n int, edges []Edge) ([]Edge, int) {
// Add the weight of the edge to the total cost
cost += edge.Weight
// Merge the sets containing the start and end vertices of the current edge
u = u.Union(int(edge.Start), int(edge.End))
u.Union(int(edge.Start), int(edge.End))
}
}

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

Expand All @@ -17,43 +18,45 @@ package graph
// Defining the union-find data structure
type UnionFind struct {
parent []int
size []int
rank []int
}

// Initialise a new union find data structure with s nodes
func NewUnionFind(s int) UnionFind {
parent := make([]int, s)
size := make([]int, s)
for k := 0; k < s; k++ {
parent[k] = k
size[k] = 1
rank := make([]int, s)
for i := 0; i < s; i++ {
parent[i] = i
rank[i] = 1
}
return UnionFind{parent, size}
return UnionFind{parent, rank}
}

// to find the root of the set to which the given element belongs, the Find function serves the purpose
func (u UnionFind) Find(q int) int {
for q != u.parent[q] {
q = u.parent[q]
// Find finds the root of the set to which the given element belongs.
// It performs path compression to make future Find operations faster.
func (u *UnionFind) Find(q int) int {
if q != u.parent[q] {
u.parent[q] = u.Find(u.parent[q])
}
return q
return u.parent[q]
}

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

if rootP == rootQ {
return u
return
}

if u.size[rootP] < u.size[rootQ] {
if u.rank[rootP] < u.rank[rootQ] {
u.parent[rootP] = rootQ
u.size[rootQ] += u.size[rootP]
} else if u.rank[rootP] > u.rank[rootQ] {
u.parent[rootQ] = rootP
} else {
u.parent[rootQ] = rootP
u.size[rootP] += u.size[rootQ]
u.rank[rootP]++
}
return u
}
23 changes: 16 additions & 7 deletions graph/unionfind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ func TestUnionFind(t *testing.T) {
u := NewUnionFind(10) // Creating a Union-Find data structure with 10 elements

//union operations
u = u.Union(0, 1)
u = u.Union(2, 3)
u = u.Union(4, 5)
u = u.Union(6, 7)
u.Union(0, 1)
u.Union(2, 3)
u.Union(4, 5)
u.Union(6, 7)

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

u = u.Union(1, 5) // Additional union operation
u = u.Union(3, 7) // Additional union operation
u.Union(1, 5) // Additional union operation
u.Union(3, 7) // Additional union operation

// Testing the parent of specific elements after more union operations
t.Run("Test Find after Union", func(t *testing.T) {
if u.Find(0) != u.Find(5) || u.Find(2) != u.Find(7) {
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) {
t.Error("Union operation not functioning correctly")
}
})

u.Union(3, 7) // Repeated union operation

// Testing that repeated union operations are idempotent
t.Run("Test Find after repeated Union", func(t *testing.T) {
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) {
t.Error("Union operation not functioning correctly")
}
})
Expand Down
Loading

0 comments on commit 4d80f04

Please sign in to comment.