mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 23:26:52 +08:00

The From method incorrectly returned nil for the empty case and node neighbour expansion did not mark visited nodes. Fix these and exercise the corrected path in the test. Also clean up the code structure.
394 lines
7.7 KiB
Go
394 lines
7.7 KiB
Go
// Copyright ©2014 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 graph_test
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/graph/iterator"
|
|
"gonum.org/v1/gonum/graph/simple"
|
|
"gonum.org/v1/gonum/graph/topo"
|
|
)
|
|
|
|
// GraphNode is a node in an implicit graph.
|
|
type GraphNode struct {
|
|
id int64
|
|
neighbors []graph.Node
|
|
roots []*GraphNode
|
|
|
|
name string
|
|
}
|
|
|
|
// NewGraphNode returns a new GraphNode.
|
|
func NewGraphNode(id int64, name string) *GraphNode {
|
|
return &GraphNode{name: name, id: id}
|
|
}
|
|
|
|
// String returns the node's name.
|
|
func (g *GraphNode) String() string {
|
|
return g.name
|
|
}
|
|
|
|
// Node allows GraphNode to satisfy the graph.Graph interface.
|
|
func (g *GraphNode) Node(id int64) graph.Node {
|
|
if id == g.id {
|
|
return g
|
|
}
|
|
|
|
seen := map[int64]struct{}{g.id: {}}
|
|
for _, root := range g.roots {
|
|
if root.ID() == id || root.has(seen, id) {
|
|
return root
|
|
}
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
if n.ID() == id {
|
|
return n
|
|
}
|
|
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
if gn.has(seen, id) {
|
|
return gn
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *GraphNode) has(seen map[int64]struct{}, id int64) bool {
|
|
for _, root := range g.roots {
|
|
if _, ok := seen[root.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[root.ID()] = struct{}{}
|
|
|
|
if root.ID() == id || root.has(seen, id) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
if _, ok := seen[n.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[n.ID()] = struct{}{}
|
|
|
|
if n.ID() == id {
|
|
return true
|
|
}
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
if gn.has(seen, id) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Nodes allows GraphNode to satisfy the graph.Graph interface.
|
|
func (g *GraphNode) Nodes() graph.Nodes {
|
|
nodes := []graph.Node{g}
|
|
seen := map[int64]struct{}{g.id: {}}
|
|
|
|
for _, root := range g.roots {
|
|
seen[root.ID()] = struct{}{}
|
|
nodes = root.nodes(append(nodes, root), seen)
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
seen[n.ID()] = struct{}{}
|
|
|
|
nodes = append(nodes, n)
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
nodes = gn.nodes(nodes, seen)
|
|
}
|
|
}
|
|
|
|
return iterator.NewOrderedNodes(nodes)
|
|
}
|
|
|
|
func (g *GraphNode) nodes(dst []graph.Node, seen map[int64]struct{}) []graph.Node {
|
|
for _, root := range g.roots {
|
|
if _, ok := seen[root.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[root.ID()] = struct{}{}
|
|
|
|
dst = root.nodes(append(dst, graph.Node(root)), seen)
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
if _, ok := seen[n.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[n.ID()] = struct{}{}
|
|
|
|
dst = append(dst, n)
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
dst = gn.nodes(dst, seen)
|
|
}
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
// From allows GraphNode to satisfy the graph.Graph interface.
|
|
func (g *GraphNode) From(id int64) graph.Nodes {
|
|
if id == g.ID() {
|
|
return iterator.NewOrderedNodes(g.neighbors)
|
|
}
|
|
|
|
seen := map[int64]struct{}{g.id: {}}
|
|
for _, root := range g.roots {
|
|
seen[root.ID()] = struct{}{}
|
|
|
|
if result := root.findNeighbors(id, seen); result != nil {
|
|
return iterator.NewOrderedNodes(result)
|
|
}
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
seen[n.ID()] = struct{}{}
|
|
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
if result := gn.findNeighbors(id, seen); result != nil {
|
|
return iterator.NewOrderedNodes(result)
|
|
}
|
|
}
|
|
}
|
|
|
|
return graph.Empty
|
|
}
|
|
|
|
func (g *GraphNode) findNeighbors(id int64, seen map[int64]struct{}) []graph.Node {
|
|
if id == g.ID() {
|
|
return g.neighbors
|
|
}
|
|
|
|
for _, root := range g.roots {
|
|
if _, ok := seen[root.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[root.ID()] = struct{}{}
|
|
|
|
if result := root.findNeighbors(id, seen); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
if _, ok := seen[n.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[n.ID()] = struct{}{}
|
|
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
if result := gn.findNeighbors(id, seen); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasEdgeBetween allows GraphNode to satisfy the graph.Graph interface.
|
|
func (g *GraphNode) HasEdgeBetween(uid, vid int64) bool {
|
|
return g.EdgeBetween(uid, vid) != nil
|
|
}
|
|
|
|
// Edge allows GraphNode to satisfy the graph.Graph interface.
|
|
func (g *GraphNode) Edge(uid, vid int64) graph.Edge {
|
|
return g.EdgeBetween(uid, vid)
|
|
}
|
|
|
|
// EdgeBetween allows GraphNode to satisfy the graph.Graph interface.
|
|
func (g *GraphNode) EdgeBetween(uid, vid int64) graph.Edge {
|
|
if uid == g.id || vid == g.id {
|
|
for _, n := range g.neighbors {
|
|
if n.ID() == uid || n.ID() == vid {
|
|
return simple.Edge{F: g, T: n}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
seen := map[int64]struct{}{g.id: {}}
|
|
for _, root := range g.roots {
|
|
seen[root.ID()] = struct{}{}
|
|
if result := root.edgeBetween(uid, vid, seen); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
seen[n.ID()] = struct{}{}
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
if result := gn.edgeBetween(uid, vid, seen); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *GraphNode) edgeBetween(uid, vid int64, seen map[int64]struct{}) graph.Edge {
|
|
if uid == g.id || vid == g.id {
|
|
for _, n := range g.neighbors {
|
|
if n.ID() == uid || n.ID() == vid {
|
|
return simple.Edge{F: g, T: n}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
for _, root := range g.roots {
|
|
if _, ok := seen[root.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[root.ID()] = struct{}{}
|
|
|
|
if result := root.edgeBetween(uid, vid, seen); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
|
|
for _, n := range g.neighbors {
|
|
if _, ok := seen[n.ID()]; ok {
|
|
continue
|
|
}
|
|
seen[n.ID()] = struct{}{}
|
|
|
|
if gn, ok := n.(*GraphNode); ok {
|
|
if result := gn.edgeBetween(uid, vid, seen); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ID allows GraphNode to satisfy the graph.Node interface.
|
|
func (g *GraphNode) ID() int64 {
|
|
return g.id
|
|
}
|
|
|
|
// AddMeighbor adds an edge between g and n.
|
|
func (g *GraphNode) AddNeighbor(n *GraphNode) {
|
|
g.neighbors = append(g.neighbors, graph.Node(n))
|
|
}
|
|
|
|
// AddRoot adds provides an entrance into the graph g from n.
|
|
func (g *GraphNode) AddRoot(n *GraphNode) {
|
|
g.roots = append(g.roots, n)
|
|
}
|
|
|
|
func Example_implicit() {
|
|
// This example shows the construction of the following graph
|
|
// using the implicit graph type above.
|
|
//
|
|
// The visual representation of the graph can be seen at
|
|
// https://graphviz.gitlab.io/_pages/Gallery/undirected/fdpclust.html
|
|
//
|
|
// graph G {
|
|
// e
|
|
// subgraph clusterA {
|
|
// a -- b
|
|
// subgraph clusterC {
|
|
// C -- D
|
|
// }
|
|
// }
|
|
// subgraph clusterB {
|
|
// d -- f
|
|
// }
|
|
// d -- D
|
|
// e -- clusterB
|
|
// clusterC -- clusterB
|
|
// }
|
|
|
|
// graph G {
|
|
G := NewGraphNode(0, "G")
|
|
// e
|
|
e := NewGraphNode(1, "e")
|
|
|
|
// subgraph clusterA {
|
|
clusterA := NewGraphNode(2, "clusterA")
|
|
// a -- b
|
|
a := NewGraphNode(3, "a")
|
|
b := NewGraphNode(4, "b")
|
|
a.AddNeighbor(b)
|
|
b.AddNeighbor(a)
|
|
clusterA.AddRoot(a)
|
|
clusterA.AddRoot(b)
|
|
|
|
// subgraph clusterC {
|
|
clusterC := NewGraphNode(5, "clusterC")
|
|
// C -- D
|
|
C := NewGraphNode(6, "C")
|
|
D := NewGraphNode(7, "D")
|
|
C.AddNeighbor(D)
|
|
D.AddNeighbor(C)
|
|
|
|
clusterC.AddRoot(C)
|
|
clusterC.AddRoot(D)
|
|
// }
|
|
clusterA.AddRoot(clusterC)
|
|
// }
|
|
|
|
// subgraph clusterB {
|
|
clusterB := NewGraphNode(8, "clusterB")
|
|
// d -- f
|
|
d := NewGraphNode(9, "d")
|
|
f := NewGraphNode(10, "f")
|
|
d.AddNeighbor(f)
|
|
f.AddNeighbor(d)
|
|
clusterB.AddRoot(d)
|
|
clusterB.AddRoot(f)
|
|
// }
|
|
|
|
// d -- D
|
|
d.AddNeighbor(D)
|
|
D.AddNeighbor(d)
|
|
|
|
// e -- clusterB
|
|
e.AddNeighbor(clusterB)
|
|
clusterB.AddNeighbor(e)
|
|
|
|
// clusterC -- clusterB
|
|
clusterC.AddNeighbor(clusterB)
|
|
clusterB.AddNeighbor(clusterC)
|
|
|
|
G.AddRoot(e)
|
|
G.AddRoot(clusterA)
|
|
G.AddRoot(clusterB)
|
|
// }
|
|
|
|
if topo.IsPathIn(G, []graph.Node{C, D, d, f}) {
|
|
fmt.Println("C--D--d--f is a path in G.")
|
|
}
|
|
|
|
fmt.Println("\nConnected components:")
|
|
for _, c := range topo.ConnectedComponents(G) {
|
|
fmt.Printf(" %s\n", c)
|
|
}
|
|
|
|
// Output:
|
|
//
|
|
// C--D--d--f is a path in G.
|
|
//
|
|
// Connected components:
|
|
// [G]
|
|
// [e clusterB clusterC]
|
|
// [d D C f]
|
|
// [clusterA]
|
|
// [a b]
|
|
}
|