mirror of
https://github.com/gonum/gonum.git
synced 2025-10-20 13:55:20 +08:00
141 lines
2.9 KiB
Go
141 lines
2.9 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 topo
|
|
|
|
import (
|
|
"reflect"
|
|
"slices"
|
|
"testing"
|
|
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/graph/internal/ordered"
|
|
"gonum.org/v1/gonum/graph/simple"
|
|
)
|
|
|
|
var undirectedCyclesInTests = []struct {
|
|
g []intset
|
|
want [][][]int64
|
|
}{
|
|
{
|
|
g: []intset{
|
|
0: linksTo(1, 2),
|
|
1: linksTo(2, 4, 5, 9),
|
|
2: linksTo(4, 7, 9),
|
|
3: linksTo(5),
|
|
4: linksTo(8),
|
|
5: linksTo(7, 8),
|
|
6: nil,
|
|
7: nil,
|
|
8: nil,
|
|
9: nil,
|
|
10: linksTo(11, 12),
|
|
11: linksTo(12),
|
|
12: nil,
|
|
},
|
|
want: [][][]int64{
|
|
{
|
|
{0, 1, 2, 0},
|
|
{1, 2, 7, 5, 1},
|
|
{1, 2, 9, 1},
|
|
{1, 4, 8, 5, 1},
|
|
{2, 4, 8, 5, 7, 2},
|
|
{10, 11, 12, 10},
|
|
},
|
|
{
|
|
{0, 1, 2, 0},
|
|
{1, 2, 4, 1},
|
|
{1, 2, 7, 5, 1},
|
|
{1, 2, 9, 1},
|
|
{1, 4, 8, 5, 1},
|
|
{10, 11, 12, 10},
|
|
},
|
|
{
|
|
{0, 1, 2, 0},
|
|
{1, 2, 4, 1},
|
|
{1, 2, 9, 1},
|
|
{1, 4, 8, 5, 1},
|
|
{2, 4, 8, 5, 7, 2},
|
|
{10, 11, 12, 10},
|
|
},
|
|
{
|
|
{0, 1, 2, 0},
|
|
{1, 2, 4, 1},
|
|
{1, 2, 7, 5, 1},
|
|
{1, 2, 9, 1},
|
|
{2, 4, 8, 5, 7, 2},
|
|
{10, 11, 12, 10},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestUndirectedCyclesIn(t *testing.T) {
|
|
for i, test := range undirectedCyclesInTests {
|
|
g := simple.NewUndirectedGraph()
|
|
g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs.
|
|
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)})
|
|
}
|
|
}
|
|
cycles := UndirectedCyclesIn(g)
|
|
var got [][]int64
|
|
if cycles != nil {
|
|
got = make([][]int64, len(cycles))
|
|
}
|
|
// Canonicalise the cycles.
|
|
for j, c := range cycles {
|
|
ids := make([]int64, len(c))
|
|
for k, n := range canonicalise(c[:len(c)-1]) {
|
|
ids[k] = n.ID()
|
|
}
|
|
ids[len(ids)-1] = ids[0]
|
|
got[j] = ids
|
|
}
|
|
ordered.BySliceValues(got)
|
|
var matched bool
|
|
for _, want := range test.want {
|
|
if reflect.DeepEqual(got, want) {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
if !matched {
|
|
t.Errorf("unexpected paton result for %d:\n\tgot:%#v\n\twant from:%#v", i, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// canonicalise returns the cycle path c cyclicly permuted such that
|
|
// the first element has the lowest ID and then conditionally
|
|
// reversed so that the second element has the lowest possible
|
|
// neighbouring ID.
|
|
// c lists each node only once - the final node must not be a
|
|
// reiteration of the first node.
|
|
func canonicalise(c []graph.Node) []graph.Node {
|
|
if len(c) < 2 {
|
|
return c
|
|
}
|
|
idx := 0
|
|
min := c[0].ID()
|
|
for i, n := range c[1:] {
|
|
if id := n.ID(); id < min {
|
|
idx = i + 1
|
|
min = id
|
|
}
|
|
}
|
|
if idx != 0 {
|
|
c = append(c[idx:], c[:idx]...)
|
|
}
|
|
if c[len(c)-1].ID() < c[1].ID() {
|
|
slices.Reverse(c[1:])
|
|
}
|
|
return c
|
|
}
|