Skip to content

Commit

Permalink
feat: add shortest path calculation using dijkstra
Browse files Browse the repository at this point in the history
  • Loading branch information
moul committed Jun 29, 2019
1 parent 7dc9d68 commit 25b1f7c
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 5 deletions.
72 changes: 71 additions & 1 deletion graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package graphman
import (
"fmt"
"log"
"math"
"sort"
"strings"
)
Expand All @@ -11,6 +12,9 @@ type Graph struct {
vertices Vertices
edges Edges
Attrs
dijkstra struct {
missing uint64
}
}

func New(attrs ...Attrs) *Graph {
Expand Down Expand Up @@ -41,9 +45,18 @@ func (g Graph) FindAllPaths(srcID, dstID string) Paths {
return paths
}

var i = 200

func (g Graph) findAllPathsRec(current, target *Vertex, prefix Path) Paths {
i--
if i < 0 {
return Paths{}
}
paths := Paths{}
for _, edge := range current.successors {
if prefix.HasVertex(edge.dst.id) {
continue
}
newPath := append(prefix, edge)
if edge.dst == target {
paths = append(paths, &newPath)
Expand All @@ -54,6 +67,60 @@ func (g Graph) findAllPathsRec(current, target *Vertex, prefix Path) Paths {
return paths
}

func (g Graph) FindShortestPath(srcID, dstID string) (Path, int64) {
src := g.GetVertex(srcID)
dst := g.GetVertex(dstID)
if src == nil || dst == nil {
return nil, -1
}

// reset dijkstra
g.dijkstra.missing = 0
for _, v := range g.vertices {
v.dijkstra.dist = math.MaxInt64
v.dijkstra.prev = nil
v.dijkstra.visited = false
g.dijkstra.missing++
}
src.dijkstra.dist = 0

// run dijkstra
for g.dijkstra.missing > 0 {
var u *Vertex
for _, v := range g.vertices {
if v.dijkstra.visited {
continue
}
if u == nil || u.dijkstra.dist > v.dijkstra.dist {
u = v
}
}
for _, e := range u.successors {
n := e.dst
if n.dijkstra.visited {
continue
}
dist := u.dijkstra.dist + 1 // 1 could be replace by a value
if dist < n.dijkstra.dist {
n.dijkstra.dist = dist
n.dijkstra.prev = e
}
}
u.dijkstra.visited = true
g.dijkstra.missing--
}

if dst.dijkstra.dist == math.MaxInt64 {
return nil, -1
}

path := Path{}
for cur := dst; cur.dijkstra.prev != nil; cur = cur.dijkstra.prev.src {
path = append(Path{cur.dijkstra.prev}, path...)
}
return path, dst.dijkstra.dist
}

func (g Graph) Edges() Edges {
// FIXME: sort.Sort(g.edges)
return g.edges
Expand Down Expand Up @@ -126,7 +193,10 @@ func (g Graph) IsolatedVertices() Vertices {
return isolated
}

func (g Graph) String() string {
func (g *Graph) String() string {
if g == nil {
return "[INVALID]"
}
elems := []string{}
for _, edge := range g.edges {
elems = append(elems, edge.String())
Expand Down
89 changes: 85 additions & 4 deletions graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,101 @@ func ExampleGraph_simple() {
// Output: {(A,B),(B,C),(E,F),D}
}

func ExampleGraph_components() {
graph := New()
// component 1: loop
graph.AddEdge("A", "B")
graph.AddEdge("B", "C")
graph.AddEdge("C", "D")
graph.AddEdge("D", "A")

// component 2: standard
graph.AddEdge("L", "M")
graph.AddEdge("M", "N")
graph.AddEdge("M", "O")
graph.AddEdge("M", "P")
graph.AddEdge("N", "Q")
graph.AddEdge("O", "Q")
graph.AddEdge("P", "Q")
graph.AddEdge("Q", "R")

// component 3: self loop
graph.AddEdge("Z", "Z")

// component 4: reverse (sorted string)
graph.AddEdge("Y", "X")
graph.AddEdge("X", "W")
graph.AddEdge("W", "V")
graph.AddEdge("V", "U")

for _, couple := range [][2]string{
{"A", "D"}, // using the loop from component 1
{"B", "A"}, // same
{"A", "L"}, // two components
{"L", "R"}, // through all the component 2
{"Z", "Z"}, // self loop
{"Y", "U"}, // reverse sorted string
} {
fmt.Printf("couple %s-%s:\n", couple[0], couple[1])
for _, path := range graph.FindAllPaths(couple[0], couple[1]) {
fmt.Println("-", path)
}
path, dist := graph.FindShortestPath(couple[0], couple[1])
fmt.Println("shortest:", path, dist)
fmt.Println()
}

// Output:
// couple A-D:
// - (A,B,C,D)
// shortest: (A,B,C,D) 3
//
// couple B-A:
// - (B,C,D,A)
// shortest: (B,C,D,A) 3
//
// couple A-L:
// shortest: [INVALID] -1
//
// couple L-R:
// - (L,M,N,Q,R)
// - (L,M,O,Q,R)
// - (L,M,P,Q,R)
// shortest: (L,M,N,Q,R) 4
//
// couple Z-Z:
// - (Z,Z)
// shortest: () 0
//
// couple Y-U:
// - (Y,X,W,V,U)
// shortest: (Y,X,W,V,U) 4
}

func ExampleGraphFindAllPaths() {
graph := New()
graph.AddEdge("G", "H")
graph.AddEdge("A", "B")
graph.AddEdge("A", "B")
graph.AddEdge("F", "H")
graph.AddEdge("A", "C")
graph.AddEdge("A", "D")
graph.AddEdge("B", "E")
graph.AddEdge("B", "G")
graph.AddEdge("C", "F")
graph.AddEdge("D", "H")
graph.AddEdge("A", "D")
graph.AddEdge("E", "G")
graph.AddEdge("E", "H")
graph.AddEdge("F", "G")
graph.AddEdge("F", "H")
graph.AddEdge("G", "H")

for _, path := range graph.FindAllPaths("A", "H") {
fmt.Println(path)
}

fmt.Println()
path, dist := graph.FindShortestPath("A", "H")
fmt.Printf("shortest (distance=%d): %s\n", dist, path)

// Output:
// (A,B,E,G,H)
// (A,B,E,G,H)
Expand All @@ -47,6 +122,8 @@ func ExampleGraphFindAllPaths() {
// (A,C,F,G,H)
// (A,C,F,H)
// (A,D,H)
//
// shortest (distance=2): (A,D,H)
}

func ExampleGraph_complex() {
Expand Down Expand Up @@ -78,5 +155,9 @@ func ExampleGraph_big() {
}

fmt.Println(graph)
// Output: {(0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10),(10,11),(11,12),(12,13),(13,14),(14,15),(15,16),(16,17),(17,18),(18,19),(19,20),(20,21),(21,22),(22,23),(23,24),(24,25),(25,26),(26,27),(27,28),(28,29),(29,30),(30,31),(31,32),(32,33),(33,34),(34,35),(35,36),(36,37),(37,38),(38,39),(39,40),(40,41),(41,42),(42,43),(43,44),(44,45),(45,46),(46,47),(47,48),(48,49),(49,50),(50,51),(51,52),(52,53),(53,54),(54,55),(55,56),(56,57),(57,58),(58,59),(59,60),(60,61),(61,62),(62,63),(63,64),(64,65),(65,66),(66,67),(67,68),(68,69),(69,70),(70,71),(71,72),(72,73),(73,74),(74,75),(75,76),(76,77),(77,78),(78,79),(79,80),(80,81),(81,82),(82,83),(83,84),(84,85),(85,86),(86,87),(87,88),(88,89),(89,90),(90,91),(91,92),(92,93),(93,94),(94,95),(95,96),(96,97),(97,98),(98,99),(99,100)}
path, dist := graph.FindShortestPath("0", "100")
fmt.Println("0-100:", path, dist)
// Output:
// {(0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(8,9),(9,10),(10,11),(11,12),(12,13),(13,14),(14,15),(15,16),(16,17),(17,18),(18,19),(19,20),(20,21),(21,22),(22,23),(23,24),(24,25),(25,26),(26,27),(27,28),(28,29),(29,30),(30,31),(31,32),(32,33),(33,34),(34,35),(35,36),(36,37),(37,38),(38,39),(39,40),(40,41),(41,42),(42,43),(43,44),(44,45),(45,46),(46,47),(47,48),(48,49),(49,50),(50,51),(51,52),(52,53),(53,54),(54,55),(55,56),(56,57),(57,58),(58,59),(59,60),(60,61),(61,62),(62,63),(63,64),(64,65),(65,66),(66,67),(67,68),(68,69),(69,70),(70,71),(71,72),(72,73),(73,74),(74,75),(75,76),(76,77),(77,78),(78,79),(79,80),(80,81),(81,82),(82,83),(83,84),(84,85),(85,86),(86,87),(87,88),(88,89),(89,90),(90,91),(91,92),(92,93),(93,94),(94,95),(95,96),(96,97),(97,98),(98,99),(99,100)}
// 0-100: (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100) 100
}
31 changes: 31 additions & 0 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ import (
"strings"
)

//
// Path
//

type Path Edges

func (p Path) String() string {
if p == nil {
return "[INVALID]"
}
vertices := p.Vertices()
ids := []string{}
for _, vertex := range vertices {
Expand All @@ -30,18 +37,42 @@ func (p Path) IsValid() bool {
}

func (p Path) Vertices() Vertices {
if len(p) < 1 {
return Vertices{}
}
vertices := Vertices{p.FirstVertex()}
for _, edge := range p {
vertices = append(vertices, edge.dst)
}
return vertices
}

func (p Path) HasVertex(id string) bool {
if len(p) < 1 {
return false
}
if p[0].src.id == id {
return true
}
for _, e := range p {
if e.dst.id == id {
return true
}
}
return false
}

func (p Path) FirstEdge() *Edge { return p[0] }
func (p Path) LastEdge() *Edge { return p[len(p)-1] }
func (p Path) FirstVertex() *Vertex { return p.FirstEdge().src }
func (p Path) LastVertex() *Vertex { return p.LastEdge().dst }

func (p Path) Edges() Edges { return Edges(p) }

//
// Paths
//

type Paths []*Path

func (p Paths) String() string {
Expand Down
5 changes: 5 additions & 0 deletions vertex.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ type Vertex struct {
successors Edges
predecessors Edges
Attrs
dijkstra struct {
dist int64
prev *Edge
visited bool
}
}

func newVertex(id string, attrs ...Attrs) *Vertex {
Expand Down

0 comments on commit 25b1f7c

Please sign in to comment.