mirror of
https://github.com/gonum/gonum.git
synced 2025-10-09 09:00:38 +08:00

The Visit function was originally intended to allow the user to visit nodes with an understanding of the path which edge was used to reach the node. This was misguided because it implies the existence of an invariant that does not exist; every edge into a node is passed to Visit as its node end points. This does not happen because Visit is only called after the node being traverse to is tested for having been visited, leading to surprising behaviour. So this change makes Visit only consider the visited node. The function that was originally intended to be provided by Visit is not given by the Traverse function — previously EdgeFilter — which is called prior to the node's visited status test. The node's visited status can be determined via the walker's Visited method if needed. To avoid painting the edge visiting method as purely a filter, it is renamed Traverse.
434 lines
10 KiB
Go
434 lines
10 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"
|
|
"sort"
|
|
"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,
|
|
}
|
|
)
|
|
|
|
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("unexepected final node for test %d:\ngot: %v\nwant: %v", i, final, test.final)
|
|
}
|
|
for _, l := range got {
|
|
sort.Sort(ordered.Int64s(l))
|
|
}
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexepected 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{0, 1, 2, 3, 4, 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{0, 1, 2, 3, 4},
|
|
},
|
|
{
|
|
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{1, 2, 3, 4, 5},
|
|
},
|
|
{
|
|
g: batageljZaversnikGraph,
|
|
from: simple.Node(13),
|
|
final: map[graph.Node]bool{nil: true},
|
|
want: []int64{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
|
|
},
|
|
}
|
|
|
|
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("unexepected final node for test %d:\ngot: %v\nwant: %v", i, final, test.final)
|
|
}
|
|
sort.Sort(ordered.Int64s(got))
|
|
if test.want != nil && !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("unexepected DFS traversed nodes for test %d:\ngot: %v\nwant: %v", i, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
sort.Sort(ordered.Int64s(ids))
|
|
got[j] = ids
|
|
}
|
|
sort.Sort(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()
|
|
gen.Gnp(g, n, p, nil)
|
|
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)
|
|
}
|