Skip to content
This repository was archived by the owner on Apr 26, 2019. It is now read-only.
Merged
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
46 changes: 46 additions & 0 deletions search/graph_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package search
import (
"container/heap"
"errors"
"fmt"
"sort"

"github.com/gonum/graph"
Expand Down Expand Up @@ -325,6 +326,51 @@ func CopyDirectedGraph(dst graph.MutableDirectedGraph, src graph.DirectedGraph)

/* Basic Graph tests */

// Unorderable is an error containing sets of unorderable graph.Nodes.
type Unorderable [][]graph.Node

// Error satisfies the error interface.
func (e Unorderable) Error() string {
const maxNodes = 10
var n int
for _, c := range e {
n += len(c)
}
if n > maxNodes {
// Don't return errors that are too long.
return fmt.Sprintf("search: no topological ordering: %d nodes in %d cyclic components", n, len(e))
}
return fmt.Sprintf("search: no topological ordering: cyclic components: %v", [][]graph.Node(e))
}

// Sort performs a topological sort of the directed graph g returning the 'from' to 'to'
// sort order. If a topological ordering is not possible, an Unorderable error is returned
// listing cyclic components in g with each cyclic component's members sorted by ID. When
// an Unorderable error is returned, each cyclic component's topological position within
// the sorted nodes is marked with a nil graph.Node.
func Sort(g graph.DirectedGraph) (sorted []graph.Node, err error) {
sccs := TarjanSCC(g)
sorted = make([]graph.Node, 0, len(sccs))
var sc Unorderable
for _, s := range sccs {
if len(s) != 1 {
sort.Sort(byID(s))
sc = append(sc, s)
sorted = append(sorted, nil)
continue
}
sorted = append(sorted, s[0])
}
if sc != nil {
for i, j := 0, len(sc)-1; i < j; i, j = i+1, j-1 {
sc[i], sc[j] = sc[j], sc[i]
}
err = sc
}
reverse(sorted)
return sorted, err
}

// TarjanSCC returns the strongly connected components of the graph g using Tarjan's algorithm.
//
// A strongly connected component of a graph is a set of vertices where it's possible to reach any
Expand Down
54 changes: 54 additions & 0 deletions search/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ var tarjanTests = []struct {

ambiguousOrder []interval
want [][]int

sortedLength int
unorderableLength int
sortable bool
}{
{
g: []set{
Expand All @@ -292,6 +296,10 @@ var tarjanTests = []struct {
{2, 3, 4, 6},
{0, 1, 7},
},

sortedLength: 1,
unorderableLength: 2,
sortable: false,
},
{
g: []set{
Expand All @@ -305,6 +313,10 @@ var tarjanTests = []struct {
{1, 2, 3},
{0},
},

sortedLength: 1,
unorderableLength: 1,
sortable: false,
},
{
g: []set{
Expand All @@ -316,6 +328,10 @@ var tarjanTests = []struct {
want: [][]int{
{0, 1, 2},
},

sortedLength: 0,
unorderableLength: 1,
sortable: false,
},
{
g: []set{
Expand All @@ -337,6 +353,9 @@ var tarjanTests = []struct {
want: [][]int{
{6}, {5}, {4}, {3}, {2}, {1}, {0},
},

sortedLength: 7,
sortable: true,
},
{
g: []set{
Expand All @@ -355,9 +374,44 @@ var tarjanTests = []struct {
{0, 1, 2},
{3, 4},
},

sortedLength: 0,
unorderableLength: 2,
sortable: false,
},
}

func TestSort(t *testing.T) {
for i, test := range tarjanTests {
g := concrete.NewDirectedGraph()
for u, e := range test.g {
// Add nodes that are not defined by an edge.
if !g.NodeExists(concrete.Node(u)) {
g.AddNode(concrete.Node(u))
}
for v := range e {
g.AddDirectedEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0)
}
}
sorted, err := search.Sort(g)
var gotSortedLen int
for _, n := range sorted {
if n != nil {
gotSortedLen++
}
}
if gotSortedLen != test.sortedLength {
t.Errorf("unexpected number of sortable nodes for test %d: got:%d want:%d", i, gotSortedLen, test.sortedLength)
}
if err == nil != test.sortable {
t.Errorf("unexpected sortability for test %d: got error: %v want: nil-error=%t", i, err, test.sortable)
}
if err != nil && len(err.(search.Unorderable)) != test.unorderableLength {
t.Errorf("unexpected number of unorderable nodes for test %d: got:%d want:%d", i, len(err.(search.Unorderable)), test.unorderableLength)
}
}
}

func TestTarjanSCC(t *testing.T) {
for i, test := range tarjanTests {
g := concrete.NewDirectedGraph()
Expand Down