mirror of
https://github.com/gonum/gonum.git
synced 2025-10-06 15:47:01 +08:00
478 lines
11 KiB
Go
478 lines
11 KiB
Go
// Copyright ©2015 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package traverse
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/graph/graphs/gen"
|
|
"gonum.org/v1/gonum/graph/internal/ordered"
|
|
"gonum.org/v1/gonum/graph/simple"
|
|
)
|
|
|
|
var (
|
|
// batageljZaversnikGraph is the example graph from
|
|
// figure 1 of http://arxiv.org/abs/cs/0310049v1
|
|
batageljZaversnikGraph = []intset{
|
|
0: nil,
|
|
|
|
1: linksTo(2, 3),
|
|
2: linksTo(4),
|
|
3: linksTo(4),
|
|
4: linksTo(5),
|
|
5: nil,
|
|
|
|
6: linksTo(7, 8, 14),
|
|
7: linksTo(8, 11, 12, 14),
|
|
8: linksTo(14),
|
|
9: linksTo(11),
|
|
10: linksTo(11),
|
|
11: linksTo(12),
|
|
12: linksTo(18),
|
|
13: linksTo(14, 15),
|
|
14: linksTo(15, 17),
|
|
15: linksTo(16, 17),
|
|
16: nil,
|
|
17: linksTo(18, 19, 20),
|
|
18: linksTo(19, 20),
|
|
19: linksTo(20),
|
|
20: nil,
|
|
}
|
|
|
|
// wpBronKerboschGraph is the example given in the Bron-Kerbosch article on wikipedia (renumbered).
|
|
// http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858
|
|
wpBronKerboschGraph = []intset{
|
|
0: linksTo(1, 4),
|
|
1: linksTo(2, 4),
|
|
2: linksTo(3),
|
|
3: linksTo(4, 5),
|
|
4: nil,
|
|
5: nil,
|
|
}
|
|
|
|
// g1595 is the graph shown in https://github.com/gonum/gonum/issues/1595 with the addition
|
|
// of an unconnected 0 node (due to limitations of intset).
|
|
g1595 = []intset{
|
|
0: nil,
|
|
1: linksTo(2, 4),
|
|
2: linksTo(3),
|
|
3: nil,
|
|
4: nil,
|
|
}
|
|
)
|
|
|
|
var breadthFirstTests = []struct {
|
|
g []intset
|
|
from graph.Node
|
|
edge func(graph.Edge) bool
|
|
until func(graph.Node, int) bool
|
|
final map[graph.Node]bool
|
|
want [][]int64
|
|
}{
|
|
{
|
|
g: wpBronKerboschGraph,
|
|
from: simple.Node(1),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{1},
|
|
{0, 2, 4},
|
|
{3},
|
|
{5},
|
|
},
|
|
},
|
|
{
|
|
g: wpBronKerboschGraph,
|
|
edge: func(e graph.Edge) bool {
|
|
// Do not traverse an edge between 3 and 5.
|
|
return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3)
|
|
},
|
|
from: simple.Node(1),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{1},
|
|
{0, 2, 4},
|
|
{3},
|
|
},
|
|
},
|
|
{
|
|
g: wpBronKerboschGraph,
|
|
from: simple.Node(1),
|
|
until: func(n graph.Node, _ int) bool { return n == simple.Node(3) },
|
|
final: map[graph.Node]bool{simple.Node(3): true},
|
|
want: [][]int64{
|
|
{1},
|
|
{0, 2, 4},
|
|
},
|
|
},
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
from: simple.Node(13),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{13},
|
|
{14, 15},
|
|
{6, 7, 8, 16, 17},
|
|
{11, 12, 18, 19, 20},
|
|
{9, 10},
|
|
},
|
|
},
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
from: simple.Node(13),
|
|
until: func(_ graph.Node, d int) bool { return d > 2 },
|
|
final: map[graph.Node]bool{
|
|
simple.Node(11): true,
|
|
simple.Node(12): true,
|
|
simple.Node(18): true,
|
|
simple.Node(19): true,
|
|
simple.Node(20): true,
|
|
},
|
|
want: [][]int64{
|
|
{13},
|
|
{14, 15},
|
|
{6, 7, 8, 16, 17},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestBreadthFirst(t *testing.T) {
|
|
for i, test := range breadthFirstTests {
|
|
g := simple.NewUndirectedGraph()
|
|
for u, e := range test.g {
|
|
// Add nodes that are not defined by an edge.
|
|
if g.Node(int64(u)) == nil {
|
|
g.AddNode(simple.Node(u))
|
|
}
|
|
for v := range e {
|
|
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
|
}
|
|
}
|
|
w := BreadthFirst{
|
|
Traverse: test.edge,
|
|
}
|
|
var got [][]int64
|
|
final := w.Walk(g, test.from, func(n graph.Node, d int) bool {
|
|
if test.until != nil && test.until(n, d) {
|
|
return true
|
|
}
|
|
if d >= len(got) {
|
|
got = append(got, []int64(nil))
|
|
}
|
|
got[d] = append(got[d], n.ID())
|
|
return false
|
|
})
|
|
if !test.final[final] {
|
|
t.Errorf("unexpected final node for test %d:\ngot: %v\nwant: %v", i, final, test.final)
|
|
}
|
|
for _, l := range got {
|
|
ordered.Int64s(l)
|
|
}
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexpected BFS level structure for test %d:\ngot: %v\nwant: %v", i, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
var depthFirstTests = []struct {
|
|
g []intset
|
|
from graph.Node
|
|
edge func(graph.Edge) bool
|
|
until func(graph.Node) bool
|
|
final map[graph.Node]bool
|
|
want [][]int64
|
|
}{
|
|
{
|
|
g: wpBronKerboschGraph,
|
|
from: simple.Node(1),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{1, 0, 4, 3, 5, 2},
|
|
{1, 0, 4, 3, 2, 5},
|
|
{1, 2, 3, 4, 0, 5},
|
|
{1, 2, 3, 5, 4, 0},
|
|
{1, 4, 0, 3, 5, 2},
|
|
{1, 4, 0, 3, 2, 5},
|
|
{1, 4, 3, 2, 5, 0},
|
|
{1, 4, 3, 5, 2, 0},
|
|
},
|
|
},
|
|
{
|
|
g: wpBronKerboschGraph,
|
|
edge: func(e graph.Edge) bool {
|
|
// Do not traverse an edge between 3 and 5.
|
|
return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3)
|
|
},
|
|
from: simple.Node(1),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{1, 0, 4, 3, 2},
|
|
{1, 2, 3, 4, 0},
|
|
{1, 4, 0, 3, 2},
|
|
{1, 4, 3, 2, 0},
|
|
},
|
|
},
|
|
{
|
|
g: wpBronKerboschGraph,
|
|
from: simple.Node(1),
|
|
until: func(n graph.Node) bool { return n == simple.Node(3) },
|
|
final: map[graph.Node]bool{simple.Node(3): true},
|
|
},
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
from: simple.Node(0),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{{0}},
|
|
},
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
from: simple.Node(3),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{3, 1, 2, 4, 5},
|
|
{3, 4, 2, 1, 5},
|
|
{3, 4, 5, 2, 1},
|
|
},
|
|
},
|
|
{
|
|
g: g1595,
|
|
from: simple.Node(1),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: [][]int64{
|
|
{1, 4, 2, 3},
|
|
{1, 2, 3, 4},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestDepthFirst(t *testing.T) {
|
|
for i, test := range depthFirstTests {
|
|
g := simple.NewUndirectedGraph()
|
|
for u, e := range test.g {
|
|
// Add nodes that are not defined by an edge.
|
|
if g.Node(int64(u)) == nil {
|
|
g.AddNode(simple.Node(u))
|
|
}
|
|
for v := range e {
|
|
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
|
}
|
|
}
|
|
w := DepthFirst{
|
|
Traverse: test.edge,
|
|
}
|
|
var got []int64
|
|
final := w.Walk(g, test.from, func(n graph.Node) bool {
|
|
if test.until != nil && test.until(n) {
|
|
return true
|
|
}
|
|
got = append(got, n.ID())
|
|
return false
|
|
})
|
|
if !test.final[final] {
|
|
t.Errorf("unexpected final node for test %d:\ngot: %v\nwant: %v", i, final, test.final)
|
|
}
|
|
var ok bool
|
|
for _, want := range test.want {
|
|
if reflect.DeepEqual(got, want) {
|
|
ok = true
|
|
break
|
|
}
|
|
}
|
|
if test.want != nil && !ok {
|
|
var wanted strings.Builder
|
|
for _, w := range test.want {
|
|
fmt.Fprintf(&wanted, " %v\n", w)
|
|
}
|
|
t.Errorf("unexpected DFS traversed nodes for test %d:\ngot: %v\nwant one of:\n%s", i, got, &wanted)
|
|
}
|
|
}
|
|
}
|
|
|
|
var walkAllTests = []struct {
|
|
g []intset
|
|
edge func(graph.Edge) bool
|
|
want [][]int64
|
|
}{
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
want: [][]int64{
|
|
{0},
|
|
{1, 2, 3, 4, 5},
|
|
{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
|
|
},
|
|
},
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
edge: func(e graph.Edge) bool {
|
|
// Do not traverse an edge between 3 and 5.
|
|
return (e.From().ID() != 4 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 4)
|
|
},
|
|
want: [][]int64{
|
|
{0},
|
|
{1, 2, 3, 4},
|
|
{5},
|
|
{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestWalkAll(t *testing.T) {
|
|
for i, test := range walkAllTests {
|
|
g := simple.NewUndirectedGraph()
|
|
|
|
for u, e := range test.g {
|
|
if g.Node(int64(u)) == nil {
|
|
g.AddNode(simple.Node(u))
|
|
}
|
|
for v := range e {
|
|
if g.Node(int64(v)) == nil {
|
|
g.AddNode(simple.Node(v))
|
|
}
|
|
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
|
}
|
|
}
|
|
type walker interface {
|
|
WalkAll(g graph.Undirected, before, after func(), during func(graph.Node))
|
|
}
|
|
for _, w := range []walker{
|
|
&BreadthFirst{},
|
|
&DepthFirst{},
|
|
} {
|
|
var (
|
|
c []graph.Node
|
|
cc [][]graph.Node
|
|
)
|
|
switch w := w.(type) {
|
|
case *BreadthFirst:
|
|
w.Traverse = test.edge
|
|
case *DepthFirst:
|
|
w.Traverse = test.edge
|
|
default:
|
|
panic(fmt.Sprintf("bad walker type: %T", w))
|
|
}
|
|
during := func(n graph.Node) {
|
|
c = append(c, n)
|
|
}
|
|
after := func() {
|
|
cc = append(cc, []graph.Node(nil))
|
|
cc[len(cc)-1] = append(cc[len(cc)-1], c...)
|
|
c = c[:0]
|
|
}
|
|
w.WalkAll(g, nil, after, during)
|
|
|
|
got := make([][]int64, len(cc))
|
|
for j, c := range cc {
|
|
ids := make([]int64, len(c))
|
|
for k, n := range c {
|
|
ids[k] = n.ID()
|
|
}
|
|
ordered.Int64s(ids)
|
|
got[j] = ids
|
|
}
|
|
ordered.BySliceValues(got)
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexpected connected components for test %d using %T:\ngot: %v\nwant:%v", i, w, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// intset is an integer set.
|
|
type intset map[int]struct{}
|
|
|
|
func linksTo(i ...int) intset {
|
|
if len(i) == 0 {
|
|
return nil
|
|
}
|
|
s := make(intset)
|
|
for _, v := range i {
|
|
s[v] = struct{}{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
var (
|
|
gnpUndirected_10_tenth = gnpUndirected(10, 0.1)
|
|
gnpUndirected_100_tenth = gnpUndirected(100, 0.1)
|
|
gnpUndirected_1000_tenth = gnpUndirected(1000, 0.1)
|
|
gnpUndirected_10_half = gnpUndirected(10, 0.5)
|
|
gnpUndirected_100_half = gnpUndirected(100, 0.5)
|
|
gnpUndirected_1000_half = gnpUndirected(1000, 0.5)
|
|
)
|
|
|
|
func gnpUndirected(n int, p float64) graph.Undirected {
|
|
g := simple.NewUndirectedGraph()
|
|
err := gen.Gnp(g, n, p, nil)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("traverse: bad test: %v", err))
|
|
}
|
|
return g
|
|
}
|
|
|
|
func benchmarkWalkAllBreadthFirst(b *testing.B, g graph.Undirected) {
|
|
n := g.Nodes().Len()
|
|
b.ResetTimer()
|
|
var bft BreadthFirst
|
|
for i := 0; i < b.N; i++ {
|
|
bft.WalkAll(g, nil, nil, nil)
|
|
}
|
|
if len(bft.visited) != n {
|
|
b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, len(bft.visited))
|
|
}
|
|
}
|
|
|
|
func BenchmarkWalkAllBreadthFirstGnp_10_tenth(b *testing.B) {
|
|
benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_tenth)
|
|
}
|
|
func BenchmarkWalkAllBreadthFirstGnp_100_tenth(b *testing.B) {
|
|
benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_tenth)
|
|
}
|
|
func BenchmarkWalkAllBreadthFirstGnp_1000_tenth(b *testing.B) {
|
|
benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_tenth)
|
|
}
|
|
func BenchmarkWalkAllBreadthFirstGnp_10_half(b *testing.B) {
|
|
benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_half)
|
|
}
|
|
func BenchmarkWalkAllBreadthFirstGnp_100_half(b *testing.B) {
|
|
benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_half)
|
|
}
|
|
func BenchmarkWalkAllBreadthFirstGnp_1000_half(b *testing.B) {
|
|
benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_half)
|
|
}
|
|
|
|
func benchmarkWalkAllDepthFirst(b *testing.B, g graph.Undirected) {
|
|
n := g.Nodes().Len()
|
|
b.ResetTimer()
|
|
var dft DepthFirst
|
|
for i := 0; i < b.N; i++ {
|
|
dft.WalkAll(g, nil, nil, nil)
|
|
}
|
|
if len(dft.visited) != n {
|
|
b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, len(dft.visited))
|
|
}
|
|
}
|
|
|
|
func BenchmarkWalkAllDepthFirstGnp_10_tenth(b *testing.B) {
|
|
benchmarkWalkAllDepthFirst(b, gnpUndirected_10_tenth)
|
|
}
|
|
func BenchmarkWalkAllDepthFirstGnp_100_tenth(b *testing.B) {
|
|
benchmarkWalkAllDepthFirst(b, gnpUndirected_100_tenth)
|
|
}
|
|
func BenchmarkWalkAllDepthFirstGnp_1000_tenth(b *testing.B) {
|
|
benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_tenth)
|
|
}
|
|
func BenchmarkWalkAllDepthFirstGnp_10_half(b *testing.B) {
|
|
benchmarkWalkAllDepthFirst(b, gnpUndirected_10_half)
|
|
}
|
|
func BenchmarkWalkAllDepthFirstGnp_100_half(b *testing.B) {
|
|
benchmarkWalkAllDepthFirst(b, gnpUndirected_100_half)
|
|
}
|
|
func BenchmarkWalkAllDepthFirstGnp_1000_half(b *testing.B) {
|
|
benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_half)
|
|
}
|