From 87d182d78ec0ec57a2726219c12007f6cd8cfcc2 Mon Sep 17 00:00:00 2001
From: Oliver Herms <oliver.herms@exaring.de>
Date: Sat, 6 Oct 2018 13:59:37 +0200
Subject: [PATCH] Adding Dijkstra's algorithm

---
 util/dijkstra/BUILD.bazel      |  15 ++
 util/dijkstra/dijkstra.go      | 139 +++++++++++++++++++
 util/dijkstra/dijkstra_test.go | 242 +++++++++++++++++++++++++++++++++
 3 files changed, 396 insertions(+)
 create mode 100644 util/dijkstra/BUILD.bazel
 create mode 100644 util/dijkstra/dijkstra.go
 create mode 100644 util/dijkstra/dijkstra_test.go

diff --git a/util/dijkstra/BUILD.bazel b/util/dijkstra/BUILD.bazel
new file mode 100644
index 00000000..3c928a11
--- /dev/null
+++ b/util/dijkstra/BUILD.bazel
@@ -0,0 +1,15 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["dijkstra.go"],
+    importpath = "github.com/bio-routing/bio-rd/util/dijkstra",
+    visibility = ["//visibility:public"],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["dijkstra_test.go"],
+    embed = [":go_default_library"],
+    deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
+)
diff --git a/util/dijkstra/dijkstra.go b/util/dijkstra/dijkstra.go
new file mode 100644
index 00000000..3ee6aee8
--- /dev/null
+++ b/util/dijkstra/dijkstra.go
@@ -0,0 +1,139 @@
+package dijkstra
+
+// Topology represents a network topology
+type Topology struct {
+	nodes map[Node]int64
+	edges map[Node]map[Node]int64
+}
+
+// Node represents a node in a graph
+type Node struct {
+	Name string
+}
+
+// Edge represents a directed edge in a graph
+type Edge struct {
+	NodeA    Node
+	NodeB    Node
+	Distance int64
+}
+
+// SPT represents a shortest path tree
+type SPT map[Node]Path
+
+// Path represents a path through a graph
+type Path struct {
+	Edges    []Edge
+	Distance int64
+}
+
+// NewTopologay creates a new topology
+func NewTopologay(nodes []Node, edges []Edge) *Topology {
+	t := &Topology{
+		nodes: make(map[Node]int64),
+		edges: make(map[Node]map[Node]int64),
+	}
+
+	for _, n := range nodes {
+		t.nodes[n] = -1
+	}
+
+	for _, e := range edges {
+		if _, ok := t.edges[e.NodeA]; !ok {
+			t.edges[e.NodeA] = make(map[Node]int64)
+		}
+
+		t.edges[e.NodeA][e.NodeB] = e.Distance
+	}
+
+	return t
+}
+
+func (t *Topology) newSPT() SPT {
+	spt := make(SPT)
+
+	for n := range t.nodes {
+		spt[n] = Path{
+			Edges:    make([]Edge, 0),
+			Distance: -1,
+		}
+	}
+
+	return spt
+}
+
+// SPT calculates the shortest path tree
+func (t *Topology) SPT(from Node) SPT {
+	spt := t.newSPT()
+
+	tmp := spt[from]
+	tmp.Distance = 0
+	spt[from] = tmp
+
+	unmarked := make(map[Node]struct{})
+	for n := range t.nodes {
+		if n == from {
+			continue
+		}
+		unmarked[n] = struct{}{}
+	}
+
+	for len(unmarked) > 0 {
+		for neighbor, distance := range t.edges[from] {
+			if spt[neighbor].Distance == -1 {
+				tmp := spt[neighbor]
+				tmp.Distance = spt[from].Distance + distance
+				tmp.Edges = make([]Edge, len(spt[from].Edges)+1)
+				copy(tmp.Edges, spt[from].Edges)
+				tmp.Edges[len(spt[from].Edges)] = Edge{
+					NodeA:    from,
+					NodeB:    neighbor,
+					Distance: distance,
+				}
+				spt[neighbor] = tmp
+				continue
+			}
+
+			if spt[from].Distance+distance < spt[neighbor].Distance {
+				tmp := spt[neighbor]
+				tmp.Distance = spt[from].Distance + distance
+				tmp.Edges = make([]Edge, len(spt[from].Edges)+1)
+				copy(tmp.Edges, spt[from].Edges)
+				tmp.Edges[len(spt[from].Edges)] = Edge{
+					NodeA:    from,
+					NodeB:    neighbor,
+					Distance: distance,
+				}
+				spt[neighbor] = tmp
+				continue
+			}
+		}
+
+		var next *Node
+		nextDistance := int64(0)
+		for candidate := range unmarked {
+			if spt[candidate].Distance == -1 {
+				continue
+			}
+
+			if next == nil {
+				tmp := candidate
+				next = &tmp
+				nextDistance = spt[candidate].Distance
+				continue
+			}
+
+			if spt[candidate].Distance < nextDistance {
+				tmp := candidate
+				next = &tmp
+				nextDistance = spt[candidate].Distance
+				continue
+			}
+		}
+
+		from = *next
+		delete(unmarked, from)
+	}
+
+	return spt
+}
diff --git a/util/dijkstra/dijkstra_test.go b/util/dijkstra/dijkstra_test.go
new file mode 100644
index 00000000..21c1c17c
--- /dev/null
+++ b/util/dijkstra/dijkstra_test.go
@@ -0,0 +1,242 @@
+package dijkstra
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSPT(t *testing.T) {
+	tests := []struct {
+		name     string
+		nodes    []Node
+		edges    []Edge
+		expected SPT
+	}{
+		{
+			name: "Test #1",
+			nodes: []Node{
+				{
+					Name: "A",
+				},
+				{
+					Name: "B",
+				},
+				{
+					Name: "C",
+				},
+				{
+					Name: "D",
+				},
+			},
+			edges: []Edge{
+				{
+					NodeA:    Node{Name: "A"},
+					NodeB:    Node{Name: "B"},
+					Distance: 1,
+				},
+				{
+					NodeA:    Node{Name: "B"},
+					NodeB:    Node{Name: "A"},
+					Distance: 1,
+				},
+				{
+					NodeA:    Node{Name: "B"},
+					NodeB:    Node{Name: "C"},
+					Distance: 5,
+				},
+				{
+					NodeA:    Node{Name: "C"},
+					NodeB:    Node{Name: "B"},
+					Distance: 5,
+				},
+				{
+					NodeA:    Node{Name: "B"},
+					NodeB:    Node{Name: "D"},
+					Distance: 1,
+				},
+				{
+					NodeA:    Node{Name: "D"},
+					NodeB:    Node{Name: "B"},
+					Distance: 1,
+				},
+				{
+					NodeA:    Node{Name: "A"},
+					NodeB:    Node{Name: "D"},
+					Distance: 5,
+				},
+				{
+					NodeA:    Node{Name: "D"},
+					NodeB:    Node{Name: "A"},
+					Distance: 5,
+				},
+				{
+					NodeA:    Node{Name: "D"},
+					NodeB:    Node{Name: "C"},
+					Distance: 1,
+				},
+				{
+					NodeA:    Node{Name: "C"},
+					NodeB:    Node{Name: "D"},
+					Distance: 1,
+				},
+			},
+			expected: SPT{
+				Node{Name: "A"}: Path{
+					Edges:    []Edge{},
+					Distance: 0,
+				},
+				Node{Name: "B"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "B"},
+							Distance: 1,
+						},
+					},
+					Distance: 1,
+				},
+				Node{Name: "C"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "B"},
+							Distance: 1,
+						},
+						{
+							NodeA:    Node{Name: "B"},
+							NodeB:    Node{Name: "D"},
+							Distance: 1,
+						},
+						{
+							NodeA:    Node{Name: "D"},
+							NodeB:    Node{Name: "C"},
+							Distance: 1,
+						},
+					},
+					Distance: 3,
+				},
+				Node{Name: "D"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "B"},
+							Distance: 1,
+						},
+						{
+							NodeA:    Node{Name: "B"},
+							NodeB:    Node{Name: "D"},
+							Distance: 1,
+						},
+					},
+					Distance: 2,
+				},
+			},
+		},
+		{
+			name: "Test #2",
+			nodes: []Node{
+				{
+					Name: "A",
+				},
+				{
+					Name: "B",
+				},
+				{
+					Name: "C",
+				},
+				{
+					Name: "D",
+				},
+				{
+					Name: "E",
+				},
+			},
+			edges: []Edge{
+				{
+					NodeA:    Node{Name: "A"},
+					NodeB:    Node{Name: "B"},
+					Distance: 1,
+				},
+				{
+					NodeA:    Node{Name: "A"},
+					NodeB:    Node{Name: "D"},
+					Distance: 2,
+				},
+				{
+					NodeA:    Node{Name: "A"},
+					NodeB:    Node{Name: "E"},
+					Distance: 3,
+				},
+				{
+					NodeA:    Node{Name: "B"},
+					NodeB:    Node{Name: "C"},
+					Distance: 10,
+				},
+				{
+					NodeA:    Node{Name: "E"},
+					NodeB:    Node{Name: "C"},
+					Distance: 5,
+				},
+			},
+			expected: SPT{
+				Node{Name: "A"}: Path{
+					Edges:    []Edge{},
+					Distance: 0,
+				},
+				Node{Name: "B"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "B"},
+							Distance: 1,
+						},
+					},
+					Distance: 1,
+				},
+				Node{Name: "C"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "E"},
+							Distance: 3,
+						},
+						{
+							NodeA:    Node{Name: "E"},
+							NodeB:    Node{Name: "C"},
+							Distance: 5,
+						},
+					},
+					Distance: 8,
+				},
+				Node{Name: "D"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "D"},
+							Distance: 2,
+						},
+					},
+					Distance: 2,
+				},
+				Node{Name: "E"}: Path{
+					Edges: []Edge{
+						{
+							NodeA:    Node{Name: "A"},
+							NodeB:    Node{Name: "E"},
+							Distance: 3,
+						},
+					},
+					Distance: 3,
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		top := NewTopologay(test.nodes, test.edges)
+		spt := top.SPT(Node{Name: "A"})
+
+		assert.Equalf(t, test.expected, spt, "Test %q", test.name)
+	}
+}
-- 
GitLab