diff --git a/search/floydwarshall.go b/search/floydwarshall.go index f08001bf..11af6d60 100644 --- a/search/floydwarshall.go +++ b/search/floydwarshall.go @@ -68,7 +68,7 @@ func FloydWarshall(g graph.Graph, weight graph.CostFunc) (paths ShortestPaths, o joint := paths.dist.At(i, k) + paths.dist.At(k, j) if ij > joint { paths.set(i, j, joint, paths.at(i, k)...) - } else if i != k && k != j && ij-joint == 0 { + } else if ij-joint == 0 { paths.add(i, j, paths.at(i, k)...) } } @@ -147,7 +147,8 @@ func (p ShortestPaths) Weight(u, v graph.Node) float64 { // Between returns a shortest path from u to v and the weight of the path. If more than // one shortest path exists between u and v, a randomly chosen path will be returned and -// unique is returned false. +// unique is returned false. If a cycle with zero weight exists in the path, it will not +// be included, but unique will be returned false. func (p ShortestPaths) Between(u, v graph.Node) (path []graph.Node, weight float64, unique bool) { from, fromOK := p.indexOf[u.ID()] to, toOK := p.indexOf[v.ID()] @@ -156,38 +157,65 @@ func (p ShortestPaths) Between(u, v graph.Node) (path []graph.Node, weight float } path = []graph.Node{p.nodes[from]} unique = true + seen := make([]int, len(p.nodes)) + for i := range seen { + seen[i] = -1 + } + seen[from] = 0 + // TODO(kortschak): Consider a more progressive approach + // to handling zero-weight cycles. One way is outlined + // here https://github.com/gonum/graph/pull/73#discussion_r31398601 for from != to { c := p.at(from, to) if len(c) != 1 { unique = false } - from = c[rand.Intn(len(c))] + i := rand.Intn(len(c)) + from = c[i] + if seen[from] >= 0 { + path = path[:seen[from]+1] + continue + } + seen[from] = len(path) path = append(path, p.nodes[from]) } // We need to re-access from in this case because from has been mutated. return path, p.dist.At(p.indexOf[u.ID()], to), unique } -// AllBetween returns all shortest paths from u to v and the weight of the paths. +// AllBetween returns all shortest paths from u to v and the weight of the paths. Paths +// containing zero-weight cycles are not returned. func (p ShortestPaths) AllBetween(u, v graph.Node) (paths [][]graph.Node, weight float64) { from, fromOK := p.indexOf[u.ID()] to, toOK := p.indexOf[v.ID()] if !fromOK || !toOK || len(p.at(from, to)) == 0 { return nil, math.Inf(1) } - paths = p.allBetween(from, to, []graph.Node{p.nodes[from]}, nil) + seen := make([]bool, len(p.nodes)) + seen[from] = true + paths = p.allBetween(from, to, seen, []graph.Node{p.nodes[from]}, nil) return paths, p.dist.At(from, to) } -func (p ShortestPaths) allBetween(from, to int, path []graph.Node, paths [][]graph.Node) [][]graph.Node { +func (p ShortestPaths) allBetween(from, to int, seen []bool, path []graph.Node, paths [][]graph.Node) [][]graph.Node { if from == to { + if path == nil { + return paths + } return append(paths, path) } - for i, from := range p.at(from, to) { - if i != 0 { + first := true + for _, from := range p.at(from, to) { + if seen[from] { + continue + } + if first { path = append([]graph.Node(nil), path...) + first = false } - paths = p.allBetween(from, to, append(path, p.nodes[from]), paths) + s := append([]bool(nil), seen...) + s[from] = true + paths = p.allBetween(from, to, s, append(path, p.nodes[from]), paths) } return paths } diff --git a/search/floydwarshall_test.go b/search/floydwarshall_test.go index 3def0684..27e66003 100644 --- a/search/floydwarshall_test.go +++ b/search/floydwarshall_test.go @@ -24,6 +24,7 @@ var floydWarshallTests = []struct { query concrete.Edge weight float64 want [][]int + unique bool none concrete.Edge }{ @@ -57,6 +58,7 @@ var floydWarshallTests = []struct { want: [][]int{ {0, 1}, }, + unique: true, none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, }, @@ -72,6 +74,7 @@ var floydWarshallTests = []struct { want: [][]int{ {0, 1}, }, + unique: true, none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, }, @@ -90,6 +93,7 @@ var floydWarshallTests = []struct { {0, 1, 2}, {0, 2}, }, + unique: false, none: concrete.Edge{concrete.Node(2), concrete.Node(1)}, }, @@ -108,6 +112,7 @@ var floydWarshallTests = []struct { {0, 1, 2}, {0, 2}, }, + unique: false, none: concrete.Edge{concrete.Node(2), concrete.Node(4)}, }, @@ -141,6 +146,7 @@ var floydWarshallTests = []struct { {0, 2, 3, 5}, {0, 5}, }, + unique: false, none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, }, @@ -174,6 +180,7 @@ var floydWarshallTests = []struct { {0, 2, 3, 5}, {0, 5}, }, + unique: false, none: concrete.Edge{concrete.Node(5), concrete.Node(6)}, }, @@ -208,6 +215,7 @@ var floydWarshallTests = []struct { {0, 2, 3, 5}, {0, 6, 5}, }, + unique: false, none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, }, @@ -242,9 +250,122 @@ var floydWarshallTests = []struct { {0, 2, 3, 5}, {0, 6, 5}, }, + unique: false, none: concrete.Edge{concrete.Node(5), concrete.Node(7)}, }, + { + name: "zero-weight cycle directed", + g: func() graph.Mutable { return concrete.NewDirectedGraph() }, + edges: []concrete.WeightedEdge{ + // Add a path from 0->4 of weight 4 + {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, + {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, + {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, + {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, + + // Add a zero-weight cycle. + {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, + {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, + }, + + query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, + weight: 4, + want: [][]int{ + {0, 1, 2, 3, 4}, + }, + unique: false, + + none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, + }, + { + name: "zero-weight cycle^2 directed", + g: func() graph.Mutable { return concrete.NewDirectedGraph() }, + edges: []concrete.WeightedEdge{ + // Add a path from 0->4 of weight 4 + {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, + {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, + {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, + {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, + + // Add a zero-weight cycle. + {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, + {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, + // With its own zero-weight cycle. + {concrete.Edge{concrete.Node(5), concrete.Node(5)}, 0}, + }, + + query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, + weight: 4, + want: [][]int{ + {0, 1, 2, 3, 4}, + }, + unique: false, + + none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, + }, + { + name: "zero-weight cycle^3 directed", + g: func() graph.Mutable { return concrete.NewDirectedGraph() }, + edges: []concrete.WeightedEdge{ + // Add a path from 0->4 of weight 4 + {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, + {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, + {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, + {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, + + // Add a zero-weight cycle. + {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, + {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, + // With its own zero-weight cycle. + {concrete.Edge{concrete.Node(5), concrete.Node(6)}, 0}, + {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 0}, + // With its own zero-weight cycle. + {concrete.Edge{concrete.Node(6), concrete.Node(6)}, 0}, + }, + + query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, + weight: 4, + want: [][]int{ + {0, 1, 2, 3, 4}, + }, + unique: false, + + none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, + }, + { + name: "zero-weight n·cycle directed", + g: func() graph.Mutable { return concrete.NewDirectedGraph() }, + edges: func() []concrete.WeightedEdge { + e := []concrete.WeightedEdge{ + // Add a path from 0->4 of weight 4 + {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, + {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, + {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, + {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, + } + next := len(e) + 1 + + // Add n zero-weight cycles. + const n = 100 + for i := 0; i < n; i++ { + e = append(e, + concrete.WeightedEdge{concrete.Edge{concrete.Node(next + i), concrete.Node(i)}, 0}, + concrete.WeightedEdge{concrete.Edge{concrete.Node(i), concrete.Node(next + i)}, 0}, + ) + } + return e + }(), + + query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, + weight: 4, + want: [][]int{ + {0, 1, 2, 3, 4}, + }, + unique: false, + + none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, + }, } func TestFloydWarshall(t *testing.T) { @@ -277,9 +398,9 @@ func TestFloydWarshall(t *testing.T) { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", test.name, weight, test.weight) } - if unique != (len(test.want) == 1) { + if unique != test.unique { t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", - test.name, unique, len(test.want) == 1) + test.name, unique, test.unique) } var got []int @@ -297,12 +418,12 @@ func TestFloydWarshall(t *testing.T) { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", test.name, p, test.want) } + } - np, weight, unique := pt.Between(test.none.From(), test.none.To()) - if np != nil || !math.IsInf(weight, 1) || unique != false { - t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", - test.name, np, weight, unique) - } + np, weight, unique := pt.Between(test.none.From(), test.none.To()) + if np != nil || !math.IsInf(weight, 1) || unique != false { + t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", + test.name, np, weight, unique) } paths, weight := pt.AllBetween(test.query.From(), test.query.To()) @@ -326,10 +447,10 @@ func TestFloydWarshall(t *testing.T) { test.name, got, test.want) } - np, weight := pt.AllBetween(test.none.From(), test.none.To()) - if np != nil || !math.IsInf(weight, 1) { + nps, weight := pt.AllBetween(test.none.From(), test.none.To()) + if nps != nil || !math.IsInf(weight, 1) { t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", - test.name, np, weight) + test.name, nps, weight) } } }