pgraph: Add a GraphCmp method
This could probably be more efficient using a known algorithm, and it could definitely require more tests, but is good enough for now.
This commit is contained in:
114
pgraph/pgraph.go
114
pgraph/pgraph.go
@@ -167,20 +167,6 @@ func (g *Graph) DeleteEdge(e Edge) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VertexMatchFn searches for a vertex in the graph and returns the vertex if
|
|
||||||
// one matches. It uses a user defined function to match. That function must
|
|
||||||
// return true on match, and an error if anything goes wrong.
|
|
||||||
func (g *Graph) VertexMatchFn(fn func(Vertex) (bool, error)) (Vertex, error) {
|
|
||||||
for v := range g.adjacency {
|
|
||||||
if b, err := fn(v); err != nil {
|
|
||||||
return nil, errwrap.Wrapf(err, "fn in VertexMatchFn() errored")
|
|
||||||
} else if b {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil // nothing found
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasVertex returns if the input vertex exists in the graph.
|
// HasVertex returns if the input vertex exists in the graph.
|
||||||
func (g *Graph) HasVertex(v Vertex) bool {
|
func (g *Graph) HasVertex(v Vertex) bool {
|
||||||
if _, exists := g.adjacency[v]; exists {
|
if _, exists := g.adjacency[v]; exists {
|
||||||
@@ -239,8 +225,8 @@ func (vs VertexSlice) Len() int { return len(vs) }
|
|||||||
func (vs VertexSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
func (vs VertexSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||||
func (vs VertexSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String() }
|
func (vs VertexSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String() }
|
||||||
|
|
||||||
// VerticesSorted returns a sorted slice of all vertices in the graph
|
// VerticesSorted returns a sorted slice of all vertices in the graph.
|
||||||
// The order is sorted by String() to avoid the non-determinism in the map type
|
// The order is sorted by String() to avoid the non-determinism in the map type.
|
||||||
func (g *Graph) VerticesSorted() []Vertex {
|
func (g *Graph) VerticesSorted() []Vertex {
|
||||||
var vertices []Vertex
|
var vertices []Vertex
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
@@ -512,6 +498,102 @@ func (g *Graph) Reachability(a, b Vertex) []Vertex {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VertexMatchFn searches for a vertex in the graph and returns the vertex if
|
||||||
|
// one matches. It uses a user defined function to match. That function must
|
||||||
|
// return true on match, and an error if anything goes wrong.
|
||||||
|
func (g *Graph) VertexMatchFn(fn func(Vertex) (bool, error)) (Vertex, error) {
|
||||||
|
for v := range g.adjacency {
|
||||||
|
if b, err := fn(v); err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "fn in VertexMatchFn() errored")
|
||||||
|
} else if b {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil // nothing found
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphCmp compares the topology of this graph to another and returns nil if
|
||||||
|
// they're equal. It uses a user defined function to compare topologically
|
||||||
|
// equivalent vertices, and edges.
|
||||||
|
// FIXME: add more test cases
|
||||||
|
func (g *Graph) GraphCmp(graph *Graph, vertexCmpFn func(Vertex, Vertex) (bool, error), edgeCmpFn func(Edge, Edge) (bool, error)) error {
|
||||||
|
n1, n2 := g.NumVertices(), graph.NumVertices()
|
||||||
|
if n1 != n2 {
|
||||||
|
return fmt.Errorf("base graph has %d vertices, while input graph has %d", n1, n2)
|
||||||
|
}
|
||||||
|
if e1, e2 := g.NumEdges(), graph.NumEdges(); e1 != e2 {
|
||||||
|
return fmt.Errorf("base graph has %d edges, while input graph has %d", e1, e2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = make(map[Vertex]Vertex) // g to graph vertex correspondence
|
||||||
|
Loop:
|
||||||
|
// check vertices
|
||||||
|
for v1 := range g.Adjacency() { // for each vertex in g
|
||||||
|
for v2 := range graph.Adjacency() { // does it match in graph ?
|
||||||
|
b, err := vertexCmpFn(v1, v2)
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrapf(err, "could not run vertexCmpFn() properly")
|
||||||
|
}
|
||||||
|
// does it match ?
|
||||||
|
if b {
|
||||||
|
m[v1] = v2 // store the mapping
|
||||||
|
continue Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("base graph, has no match in input graph for: %s", v1)
|
||||||
|
}
|
||||||
|
// vertices match :)
|
||||||
|
|
||||||
|
// is the mapping the right length?
|
||||||
|
if n1 := len(m); n1 != n2 {
|
||||||
|
return fmt.Errorf("mapping only has correspondence of %d, when it should have %d", n1, n2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if mapping is unique (are there duplicates?)
|
||||||
|
m1 := []Vertex{}
|
||||||
|
m2 := []Vertex{}
|
||||||
|
for k, v := range m {
|
||||||
|
if VertexContains(k, m1) {
|
||||||
|
return fmt.Errorf("mapping from %s is used more than once to: %s", k, m1)
|
||||||
|
}
|
||||||
|
if VertexContains(v, m2) {
|
||||||
|
return fmt.Errorf("mapping to %s is used more than once from: %s", v, m2)
|
||||||
|
}
|
||||||
|
m1 = append(m1, k)
|
||||||
|
m2 = append(m2, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check edges
|
||||||
|
for v1 := range g.Adjacency() { // for each vertex in g
|
||||||
|
v2 := m[v1] // lookup in map to get correspondance
|
||||||
|
// g.Adjacency()[v1] corresponds to graph.Adjacency()[v2]
|
||||||
|
if e1, e2 := len(g.Adjacency()[v1]), len(graph.Adjacency()[v2]); e1 != e2 {
|
||||||
|
return fmt.Errorf("base graph, vertex(%s) has %d edges, while input graph, vertex(%s) has %d", v1, e1, v2, e2)
|
||||||
|
}
|
||||||
|
|
||||||
|
for vv1, ee1 := range g.Adjacency()[v1] {
|
||||||
|
vv2 := m[vv1]
|
||||||
|
ee2 := graph.Adjacency()[v2][vv2]
|
||||||
|
|
||||||
|
// these are edges from v1 -> vv1 via ee1 (graph 1)
|
||||||
|
// to cmp to edges from v2 -> vv2 via ee2 (graph 2)
|
||||||
|
|
||||||
|
// check: (1) vv1 == vv2 ? (we've already checked this!)
|
||||||
|
|
||||||
|
// check: (2) ee1 == ee2
|
||||||
|
b, err := edgeCmpFn(ee1, ee2)
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrapf(err, "could not run edgeCmpFn() properly")
|
||||||
|
}
|
||||||
|
if !b {
|
||||||
|
return fmt.Errorf("base graph edge(%s) doesn't match input graph edge(%s)", ee1, ee2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // success!
|
||||||
|
}
|
||||||
|
|
||||||
// VertexContains is an "in array" function to test for a vertex in a slice of vertices.
|
// VertexContains is an "in array" function to test for a vertex in a slice of vertices.
|
||||||
func VertexContains(needle Vertex, haystack []Vertex) bool {
|
func VertexContains(needle Vertex, haystack []Vertex) bool {
|
||||||
for _, v := range haystack {
|
for _, v := range haystack {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package pgraph
|
package pgraph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -657,3 +658,38 @@ func TestPgraphDelete1(t *testing.T) {
|
|||||||
t.Errorf("should have 0 vertices instead of: %d", i)
|
t.Errorf("should have 0 vertices instead of: %d", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func vertexCmpFn(v1, v2 Vertex) (bool, error) {
|
||||||
|
if v1.String() == "" || v2.String() == "" {
|
||||||
|
return false, fmt.Errorf("oops, empty vertex")
|
||||||
|
}
|
||||||
|
return v1.String() == v2.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func edgeCmpFn(e1, e2 Edge) (bool, error) {
|
||||||
|
if e1.String() == "" || e2.String() == "" {
|
||||||
|
return false, fmt.Errorf("oops, empty edge")
|
||||||
|
}
|
||||||
|
return e1.String() == e2.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPgraphGraphCmp1(t *testing.T) {
|
||||||
|
g1 := &Graph{}
|
||||||
|
g2 := &Graph{}
|
||||||
|
g3 := &Graph{}
|
||||||
|
g3.AddVertex(NV("v1"))
|
||||||
|
g4 := &Graph{}
|
||||||
|
g4.AddVertex(NV("v2"))
|
||||||
|
|
||||||
|
if err := g1.GraphCmp(g2, vertexCmpFn, edgeCmpFn); err != nil {
|
||||||
|
t.Errorf("should have no error during GraphCmp, but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g1.GraphCmp(g3, vertexCmpFn, edgeCmpFn); err == nil {
|
||||||
|
t.Errorf("should have error during GraphCmp, but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g3.GraphCmp(g4, vertexCmpFn, edgeCmpFn); err == nil {
|
||||||
|
t.Errorf("should have error during GraphCmp, but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,8 +117,9 @@ func NewNoopResTest(name string) *NoopResTest {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphCmp compares the topology of two graphs and returns nil if they're equal
|
// GraphCmp compares the topology of two graphs and returns nil if they're
|
||||||
// It also compares if grouped element groups are identical
|
// equal. It also compares if grouped element groups are identical.
|
||||||
|
// TODO: port this to use the pgraph.GraphCmp function instead.
|
||||||
func GraphCmp(g1, g2 *pgraph.Graph) error {
|
func GraphCmp(g1, g2 *pgraph.Graph) error {
|
||||||
if n1, n2 := g1.NumVertices(), g2.NumVertices(); n1 != n2 {
|
if n1, n2 := g1.NumVertices(), g2.NumVertices(); n1 != n2 {
|
||||||
return fmt.Errorf("graph g1 has %d vertices, while g2 has %d", n1, n2)
|
return fmt.Errorf("graph g1 has %d vertices, while g2 has %d", n1, n2)
|
||||||
|
|||||||
Reference in New Issue
Block a user