Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added code related to graphs in golang for 2 algorithms #360

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions graph/graphs_with_golang/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/drakcoder/graphlib v0.0.0-20220129063516-644b7f615da7 h1:MWmyV2YVL3EbbBAz8nM/271q7yMPfNAXMu4HDljWikc=
github.com/drakcoder/graphlib v0.0.0-20220129063516-644b7f615da7/go.mod h1:uCqSbwzsddGxHm44kyMwKG7P4dMynatq3HlHYNWbExw=
215 changes: 215 additions & 0 deletions graph/graphs_with_golang/graphlib/graphlib.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package graphlib

//Node struct for the graph
type Node struct {
Name string
links []Edge
}

//Edge struct for the graph
type Edge struct {
from *Node
to *Node
cost uint
}

//The final graph struct
type Graph struct {
nodes map[string]*Node
exists map[string]bool
GraphType string
}

//Function to create a graph struct
func NewGraph(GT string) *Graph {
if GT != "undirected" && GT != "directed" {
panic("The argument for graph creation can be \"directed\" or \"undirected\" only")
}
return &Graph{nodes: map[string]*Node{}, exists: map[string]bool{}, GraphType: GT}
}

//Function to add nodes to the graph object
func (g *Graph) AddNodes(names []string) {
for _, name := range names {
if _, ok := g.nodes[name]; !ok {
g.nodes[name] = &Node{Name: name, links: []Edge{}}
g.exists[name] = true
}
}
}

//Function to create links between the nodes within the graph along with the cost of traversal
func (g *Graph) AddLink(a, b string, cost int) {
aNode := g.nodes[a]
bNode := g.nodes[b]
if aNode == nil || bNode == nil {
panic("creating edge for node that does not exist!")
}
aNode.links = append(aNode.links, Edge{from: aNode, to: bNode, cost: uint(cost)})
if g.GraphType == "undirected" {
bNode.links = append(bNode.links, Edge{from: bNode, to: aNode, cost: uint(cost)})
}
}

//This function finds the shortest distance between the source and destination nodes along with the path using Dijkstra's algorithm
func (g *Graph) DistBetn(source string, destination string) ([]string, uint) {
//initialising the dist and prev arrays for finding the minimum distances
dist, prev := map[string]uint{}, map[string]string{}
var path []string
//function to reverse the array of nodes
reverse := func(input []string) []string {
var res []string
for i := len(input) - 1; i >= 0; i-- {
res = append(res, input[i])
}
return res
}
//function to find the next node to be processed, i.e, the node which has the minimum distance from the source node
getClosestNonVisitedNode := func(dist map[string]uint, visited map[string]bool) string {
lowestCost := INFINITY
lowestNode := ""
for key, dis := range dist {
if _, ok := visited[key]; dis == INFINITY || ok {
continue
}
if dis < lowestCost {
lowestCost = dis
lowestNode = key
}
}
return lowestNode
}
//error check for node existence
if !g.exists[source] || !g.exists[destination] {
panic("one of the nodes does not exist!")
}
//initialising the distance and previous maps
for _, node := range g.nodes {
dist[node.Name] = INFINITY
prev[node.Name] = ""
}
//initialising the visited map to check the already visited nodes
visited := map[string]bool{}
//setting the distance from source to 0
dist[source] = 0
//looping over the nodes to populate the distance and previous maps
for u := source; u != ""; u = getClosestNonVisitedNode(dist, visited) {
//checking if source is destination to return the distance as 0
if source == destination {
break
}
//setting current distance to the current traversal node
currDist := dist[u]
for _, link := range g.nodes[u].links {
if _, ok := visited[link.to.Name]; ok {
continue
}
//initialising alternate distance
alt := currDist + link.cost
v := link.to.Name
//comparing and assigning the new values
if alt < dist[v] {
dist[v] = alt
prev[v] = u
}
}
//setting the visited value to the node as true
visited[u] = true
}
cur := destination
//finding the path
for cur != "" {
path = append(path, cur)
cur = prev[cur]
}
path = reverse(path)
//return the shortest path and distance
return path, dist[destination]
}

const INFINITY = ^uint(0)

//similar to the previous algorithm, but will give the shortest path to all the nodes form a particular source
func (g *Graph) Dijkstra(source string) (map[string]uint, map[string]string) {
getClosestNonVisitedNode := func(dist map[string]uint, visited map[string]bool) string {
lowestCost := INFINITY
lowestNode := ""
for key, dis := range dist {
if _, ok := visited[key]; dis == INFINITY || ok {
continue
}
if dis < lowestCost {
lowestCost = dis
lowestNode = key
}
}
return lowestNode
}
dist, prev := map[string]uint{}, map[string]string{}

for _, node := range g.nodes {
dist[node.Name] = INFINITY
prev[node.Name] = ""
}
visited := map[string]bool{}
dist[source] = 0
for u := source; u != ""; u = getClosestNonVisitedNode(dist, visited) {
// fmt.Println(u)
currDist := dist[u]
for _, link := range g.nodes[u].links {
if _, ok := visited[link.to.Name]; ok {
continue
}
alt := currDist + link.cost
v := link.to.Name
if alt < dist[v] {
dist[v] = alt
prev[v] = u
}
}
visited[u] = true
}
return dist, prev
}

//Topological sorting of a graph (Kahn's Algorithm)
func (g *Graph) TopologicalSort() []string {
//initialising the in-degree map for verification
in_degree := make(map[string]int)
//initialising in-degree for all the nodes
for name := range g.nodes {
for _, link := range g.nodes[name].links {
if _, ok := in_degree[name]; !ok {
in_degree[link.to.Name] = 1
} else {
in_degree[link.to.Name]++
}
}
}
// setting up the queue with nodes with in-degree 0
var q []string
for name := range g.nodes {
if in_degree[name] == 0 {
q = append(q, name)
}
}
cnt := 0
var result []string
for len(q) > 0 {
cur := q[0]
q = q[1:]
result = append(result, cur)
for _, link := range g.nodes[cur].links {
in_degree[link.to.Name]--
if in_degree[link.to.Name] == 0 {
q = append(q, link.to.Name)
}
}
cnt++
}
//this condition occurs only when there is a loop in the graph
if cnt != len(g.nodes) {
panic("there exists a cycle in the graph")
}
return result
}
127 changes: 127 additions & 0 deletions graph/graphs_with_golang/graphlib/graphmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package graphlib

import (
"math"
"strconv"
)

//point structure for map-graph
type Point struct {
X uint
Y uint
}

//Node strucure for map-graph
type MapNode struct {
point Point
name string
Links []MapEdge
}

//Edge structure for map-graph
type MapEdge struct {
Dist uint
from *MapNode
to *MapNode
}

// map-graph structure
type MapGraph struct {
nodes map[string]*MapNode
CoorExists map[string]bool
NodeExists map[string]bool
}

//Function to create a new mapGraph
func NewMapGraph() *MapGraph {
return &MapGraph{nodes: map[string]*MapNode{}, CoorExists: map[string]bool{}, NodeExists: map[string]bool{}}
}

//function to add nodes to the graph along with heurestics
func (g *MapGraph) AddNodes(names map[string][]uint) {
for name, coordinates := range names {
if len(coordinates) != 2 {
panic("there can only be 2 coordinates for each node")
}
coor := strconv.FormatUint(uint64(coordinates[0]), 10) + strconv.FormatUint(uint64(coordinates[1]), 10)
if g.CoorExists[coor] {
panic("same coordinates cannot have different points")
}
if g.NodeExists[name] {
panic("points cannot have the same names")
}
g.CoorExists[coor] = true
g.NodeExists[name] = true
g.nodes[name] = &MapNode{point: Point{X: coordinates[0], Y: coordinates[1]}, name: name, Links: []MapEdge{}}
}
}

//function to add links between the map nodes
func (g *MapGraph) CreatePath(from, to, ptype string, dist uint) {
if ptype != "bi" && ptype != "u" {
panic("path type can be either \"bi\"(bidirectional) or \"u\"(unidirectional)")
}
toNode := g.nodes[to]
fromNode := g.nodes[from]
if toNode == nil || fromNode == nil {
panic("creating edge for node that does not exist!")
}
fromNode.Links = append(fromNode.Links, MapEdge{from: fromNode, to: toNode, Dist: dist})
if ptype == "bi" {
toNode.Links = append(toNode.Links, MapEdge{to: fromNode, from: toNode, Dist: dist})
}
}

//function to demonstrate A star algorithm using heuristics
//refer to the DistBetn function in the previous file for explanation of the function
//A-start uses heurestic distances to compare and traverse to the next node which may lead to more time efficient results
func (g *MapGraph) AStar(source string) (map[string]uint, map[string]string) {
GetNext := func(hdist, visited map[string]uint) string {
min := INFINITY
u := ""
for key, value := range hdist {
if _, ok := visited[key]; ok || value == INFINITY {
continue
} else if min > value {
min = value
u = key
}
}
return u
}
dist, hdist, prev := map[string]uint{}, map[string]uint{}, map[string]string{}
if _, ok := g.nodes[source]; !ok {
panic("the given source node does not exist")
}
for _, node := range g.nodes {
dist[node.name] = INFINITY
hdist[node.name] = INFINITY
prev[node.name] = ""
}
CalculateDist := func(p1, p2 Point) float64 {
return math.Sqrt(float64(float64((p1.X-p2.X))*float64((p1.X-p2.X)) + float64((p1.Y-p2.Y))*float64((p1.Y-p2.Y))))
}

dist[source] = 0
hdist[source] = 0

visited := map[string]uint{}
//the next node is mainly obtained by using the heuristic distances rather than the original distances
for u := source; u != ""; u = GetNext(hdist, visited) {
visited[u] = 1
for _, link := range g.nodes[u].Links {
if _, ok := visited[link.to.name]; !ok {
cdist := dist[u]
alt := cdist + link.Dist
if alt < dist[link.to.name] {
dist[link.to.name] = alt
hdist[link.to.name] = alt + uint(CalculateDist(g.nodes[source].point, g.nodes[u].point))
prev[link.to.name] = u
}
}
}
}

return dist, prev

}
39 changes: 39 additions & 0 deletions graph/graphs_with_golang/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"fmt"

"github.com/drakcoder/graphlib"
)

func main() {
g := graphlib.NewGraph("directed")
g.AddNodes([]string{"a", "b", "c", "d", "e"})
g.AddLink("a", "b", 6)
g.AddLink("d", "a", 1)
g.AddLink("b", "e", 2)
g.AddLink("b", "d", 1)
g.AddLink("c", "e", 5)
g.AddLink("c", "b", 5)
g.AddLink("e", "d", 1)
g.AddLink("e", "c", 4)
// dist, prev := g.Dijkstra("a")
path1, dist1 := g.DistBetn("a", "e")
fmt.Println(dist1, path1)

mg := graphlib.NewMapGraph()
mg.AddNodes(map[string][]uint{
"a": {1, 2},
"b": {2, 4},
"c": {6, 7},
"d": {10, 11},
})
mg.CreatePath("a", "b", "bi", 10)
mg.CreatePath("a", "c", "bi", 20)
mg.CreatePath("a", "d", "bi", 30)
mg.CreatePath("b", "c", "bi", 40)
mg.CreatePath("b", "d", "bi", 50)
mg.CreatePath("c", "d", "bi", 60)
path2, dist2 := mg.AStar("a")
fmt.Println(path2, dist2)
}