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