graph: implement Join/Disjoin
This commit is contained in:
parent
20b2a80a3c
commit
b51935fcd1
@ -5,6 +5,8 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Value wraps a go value in a way such that it will be uniquely identified
|
||||
@ -111,6 +113,15 @@ func (g Graph) cp() Graph {
|
||||
return g2
|
||||
}
|
||||
|
||||
func (g Graph) String() string {
|
||||
edgeIDs := make([]string, 0, len(g.m))
|
||||
for edgeID := range g.m {
|
||||
edgeIDs = append(edgeIDs, edgeID)
|
||||
}
|
||||
sort.Strings(edgeIDs)
|
||||
return "Graph{" + strings.Join(edgeIDs, ",") + "}"
|
||||
}
|
||||
|
||||
// Add returns a new Graph instance with the given Edge added to it. If the
|
||||
// original Graph already had that Edge this returns the original Graph.
|
||||
func (g Graph) Add(e Edge) Graph {
|
||||
@ -120,12 +131,16 @@ func (g Graph) Add(e Edge) Graph {
|
||||
}
|
||||
|
||||
g2 := g.cp()
|
||||
g2.m[id] = e
|
||||
g2.vIns.add(e.Head.ID, id)
|
||||
g2.vOuts.add(e.Tail.ID, id)
|
||||
g2.addDirty(id, e)
|
||||
return g2
|
||||
}
|
||||
|
||||
func (g Graph) addDirty(edgeID string, e Edge) {
|
||||
g.m[edgeID] = e
|
||||
g.vIns.add(e.Head.ID, edgeID)
|
||||
g.vOuts.add(e.Tail.ID, edgeID)
|
||||
}
|
||||
|
||||
// Del returns a new Graph instance without the given Edge in it. If the
|
||||
// original Graph didn't have that Edge this returns the original Graph.
|
||||
func (g Graph) Del(e Edge) Graph {
|
||||
@ -141,6 +156,72 @@ func (g Graph) Del(e Edge) Graph {
|
||||
return g2
|
||||
}
|
||||
|
||||
// Disjoin looks at the whole Graph and returns all sub-graphs of it which don't
|
||||
// share any Edges between each other.
|
||||
func (g Graph) Disjoin() []Graph {
|
||||
valM := make(map[string]*Graph, len(g.vOuts))
|
||||
graphForEdge := func(edge Edge) *Graph {
|
||||
headGraph := valM[edge.Head.ID]
|
||||
tailGraph := valM[edge.Tail.ID]
|
||||
if headGraph == nil && tailGraph == nil {
|
||||
newGraph := Graph{}.cp() // cp also initializes
|
||||
return &newGraph
|
||||
} else if headGraph == nil && tailGraph != nil {
|
||||
return tailGraph
|
||||
} else if headGraph != nil && tailGraph == nil {
|
||||
return headGraph
|
||||
} else if headGraph == tailGraph {
|
||||
return headGraph // doesn't matter which is returned
|
||||
}
|
||||
|
||||
// the two values are part of different graphs, join the smaller into
|
||||
// the larger and change all values which were pointing to it to point
|
||||
// into the larger (which will then be the join of them)
|
||||
if len(tailGraph.m) > len(headGraph.m) {
|
||||
tailGraph, headGraph = headGraph, tailGraph
|
||||
}
|
||||
for edgeID, edge := range tailGraph.m {
|
||||
(*headGraph).addDirty(edgeID, edge)
|
||||
}
|
||||
for valID, valGraph := range valM {
|
||||
if valGraph == tailGraph {
|
||||
valM[valID] = headGraph
|
||||
}
|
||||
}
|
||||
return headGraph
|
||||
}
|
||||
|
||||
for edgeID, edge := range g.m {
|
||||
graph := graphForEdge(edge)
|
||||
(*graph).addDirty(edgeID, edge)
|
||||
valM[edge.Head.ID] = graph
|
||||
valM[edge.Tail.ID] = graph
|
||||
}
|
||||
|
||||
found := map[*Graph]bool{}
|
||||
graphs := make([]Graph, 0, len(valM))
|
||||
for _, graph := range valM {
|
||||
if found[graph] {
|
||||
continue
|
||||
}
|
||||
found[graph] = true
|
||||
graphs = append(graphs, *graph)
|
||||
}
|
||||
return graphs
|
||||
}
|
||||
|
||||
// Join returns a new Graph which shares all Edges of this Graph and all given
|
||||
// Graphs.
|
||||
func (g Graph) Join(graphs ...Graph) Graph {
|
||||
g2 := g.cp()
|
||||
for _, graph := range graphs {
|
||||
for edgeID, edge := range graph.m {
|
||||
g2.addDirty(edgeID, edge)
|
||||
}
|
||||
}
|
||||
return g2
|
||||
}
|
||||
|
||||
// Edges returns all Edges which are part of the Graph
|
||||
func (g Graph) Edges() []Edge {
|
||||
edges := make([]Edge, 0, len(g.m))
|
||||
@ -237,6 +318,8 @@ func (g Graph) Traverse(start Value, next func(n Node) (Value, bool)) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO VisitBreadth/VisitDepth
|
||||
|
||||
func (g Graph) edgesShared(g2 Graph) bool {
|
||||
for id := range g2.m {
|
||||
if _, ok := g.m[id]; !ok {
|
||||
|
@ -179,3 +179,82 @@ func TestSubGraphAndEqual(t *T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisjoinUnion(t *T) {
|
||||
t.Parallel()
|
||||
type state struct {
|
||||
g Graph
|
||||
// prefix -> Values with that prefix. contains dupes
|
||||
valM map[string][]Value
|
||||
disjM map[string]Graph
|
||||
}
|
||||
|
||||
type params struct {
|
||||
prefix string
|
||||
e Edge
|
||||
}
|
||||
|
||||
chk := mchk.Checker{
|
||||
Init: func() mchk.State {
|
||||
return state{
|
||||
valM: map[string][]Value{},
|
||||
disjM: map[string]Graph{},
|
||||
}
|
||||
},
|
||||
Next: func(ss mchk.State) mchk.Action {
|
||||
s := ss.(state)
|
||||
prefix := mrand.Hex(1)
|
||||
var edge Edge
|
||||
if vals := s.valM[prefix]; len(vals) == 0 {
|
||||
edge = Edge{
|
||||
Tail: strV(prefix + mrand.Hex(1)),
|
||||
Head: strV(prefix + mrand.Hex(1)),
|
||||
}
|
||||
} else if mrand.Intn(2) == 0 {
|
||||
edge = Edge{
|
||||
Tail: mrand.Element(vals, nil).(Value),
|
||||
Head: strV(prefix + mrand.Hex(1)),
|
||||
}
|
||||
} else {
|
||||
edge = Edge{
|
||||
Tail: strV(prefix + mrand.Hex(1)),
|
||||
Head: mrand.Element(vals, nil).(Value),
|
||||
}
|
||||
}
|
||||
|
||||
return mchk.Action{Params: params{prefix: prefix, e: edge}}
|
||||
},
|
||||
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
|
||||
s, p := ss.(state), a.Params.(params)
|
||||
s.g = s.g.Add(p.e)
|
||||
s.valM[p.prefix] = append(s.valM[p.prefix], p.e.Head, p.e.Tail)
|
||||
s.disjM[p.prefix] = s.disjM[p.prefix].Add(p.e)
|
||||
|
||||
var aa []massert.Assertion
|
||||
|
||||
// test Disjoin
|
||||
disj := s.g.Disjoin()
|
||||
for prefix, graph := range s.disjM {
|
||||
aa = append(aa, massert.Comment(
|
||||
massert.Equal(true, graph.Equal(s.disjM[prefix])),
|
||||
"prefix:%q", prefix,
|
||||
))
|
||||
}
|
||||
aa = append(aa, massert.Len(disj, len(s.disjM)))
|
||||
|
||||
// now test Join
|
||||
join := (Graph{}).Join(disj...)
|
||||
aa = append(aa, massert.Equal(true, s.g.Equal(join)))
|
||||
|
||||
return s, massert.All(aa...).Assert()
|
||||
},
|
||||
MaxLength: 100,
|
||||
// Each action is required for subsequent ones to make sense, so
|
||||
// minimizing won't work
|
||||
DontMinimize: true,
|
||||
}
|
||||
|
||||
if err := chk.RunFor(5 * time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user