gg: rename Walk to Iter, and implement Disjoin

This commit is contained in:
Brian Picciano 2018-06-07 02:21:44 +00:00
parent efa7319555
commit b906697151
4 changed files with 165 additions and 40 deletions

120
gg/gg.go
View File

@ -8,22 +8,6 @@ import (
"strings"
)
// TODO it's a bit unfortunate that it's possible to create disjointed graphs
// within the same graph instance. That's not really something that would be
// possible with any other type of datastructure. I think all that would be
// needed to get rid of this is to remove the Null instance and instead do a
// New(value) function. This would allow a graph to be just a single value with
// no edges, but I _think_ that's fine?
//
// Actually, that's kinda bogus, it really messes with how I conceptualized
// ginger being used. Which is maybe fine. I should do some research on the
// typical way that graphs and other structures are defined.
//
// It seems that disjoint unions, as they're called, are an accepted thing... if
// I need it for gim a Disjoin method might be the best bet, which would walk
// through the Graph and compute each disjointed Graph and return them
// individually.
// Value wraps a go value in a way such that it will be uniquely identified
// within any Graph and between Graphs. Use NewValue to create a Value instance.
// You can create an instance manually as long as ID is globally unique.
@ -173,7 +157,7 @@ func (g *Graph) cp() *Graph {
////////////////////////////////////////////////////////////////////////////////
// Graph creation
func mkVertex(typ VertexType, val Value, ins []OpenEdge) vertex {
func mkVertex(typ VertexType, val Value, ins ...OpenEdge) vertex {
v := vertex{VertexType: typ, in: ins}
switch typ {
case ValueVertex:
@ -199,7 +183,7 @@ func mkVertex(typ VertexType, val Value, ins []OpenEdge) vertex {
// multiple ValueOut OpenEdges constructed with the same val will be leaving the
// same Vertex instance in the constructed Graph.
func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{fromV: mkVertex(ValueVertex, val, nil), val: edgeVal}
return OpenEdge{fromV: mkVertex(ValueVertex, val), val: edgeVal}
}
// JunctionOut creates a OpenEdge which, when used to construct a Graph,
@ -209,9 +193,9 @@ func ValueOut(val, edgeVal Value) OpenEdge {
// When constructing Graphs Junction vertices are de-duplicated on their input
// edges. So multiple Junction OpenEdges constructed with the same set of input
// edges will be leaving the same Junction instance in the constructed Graph.
func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
func JunctionOut(ins []OpenEdge, edgeVal Value) OpenEdge {
return OpenEdge{
fromV: mkVertex(JunctionVertex, Value{}, in),
fromV: mkVertex(JunctionVertex, Value{}, ins...),
val: edgeVal,
}
}
@ -221,7 +205,7 @@ func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
// referenced within toe OpenEdge which do not yet exist in the Graph will also
// be created in this step.
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
to := mkVertex(ValueVertex, val, nil)
to := mkVertex(ValueVertex, val)
toID := to.id
// if to is already in the graph, pull it out, as it might have existing in
@ -269,7 +253,7 @@ func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
// OpenEdge, no changes are made. Any vertices referenced by toe OpenEdge for
// which that edge is their only outgoing edge will be removed from the Graph.
func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
to := mkVertex(ValueVertex, val, nil)
to := mkVertex(ValueVertex, val)
toID := to.id
// pull to out of the graph. if it's not there then bail
@ -343,6 +327,8 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
// Union takes in another Graph and returns a new one which is the union of the
// two. Value vertices which are shared between the two will be merged so that
// the new vertex has the input edges of both.
//
// TODO it bothers me that the opposite of Disjoin is Union and not "Join"
func (g *Graph) Union(g2 *Graph) *Graph {
g = g.cp()
for vID, v2 := range g2.vM {
@ -361,6 +347,80 @@ func (g *Graph) Union(g2 *Graph) *Graph {
return g
}
// Disjoin splits the Graph into as many independently connected Graphs as it
// contains. Each Graph returned will have vertices connected only within itself
// and not across to the other Graphs, and the Union of all returned Graphs will
// be the original again.
//
// The order of the Graphs returned is not deterministic.
//
// Null.Disjoin() returns empty slice.
func (g *Graph) Disjoin() []*Graph {
m := map[string]*Graph{} // maps each id to the Graph it belongs to
mG := map[*Graph]struct{}{} // tracks unique Graphs created
var connectedTo func(vertex) *Graph
connectedTo = func(v vertex) *Graph {
if v.VertexType == ValueVertex {
if g := m[v.id]; g != nil {
return g
}
}
for _, oe := range v.in {
if g := connectedTo(oe.fromV); g != nil {
return g
}
}
return nil
}
// used upon finding out that previously-thought-to-be disconnected vertices
// aren't. Merges the two graphs they're connected into together into one
// and updates all state internal to this function accordingly.
rejoin := func(gDst, gSrc *Graph) {
for id, v := range gSrc.vM {
gDst.vM[id] = v
m[id] = gDst
}
delete(mG, gSrc)
}
var connectTo func(vertex, *Graph)
connectTo = func(v vertex, g *Graph) {
if v.VertexType == ValueVertex {
if g2, ok := m[v.id]; ok && g != g2 {
rejoin(g, g2)
}
m[v.id] = g
}
for _, oe := range v.in {
connectTo(oe.fromV, g)
}
}
for id, v := range g.vM {
gV := connectedTo(v)
// if gV is nil it means this vertex is part of a new Graph which
// nothing else has been connected to yet.
if gV == nil {
gV = Null.cp()
mG[gV] = struct{}{}
}
gV.vM[id] = v
// do this no matter what, because we want to descend in to the in edges
// and mark all of those as being part of this graph too
connectTo(v, gV)
}
gg := make([]*Graph, 0, len(mG))
for g := range mG {
gg = append(gg, g)
}
return gg
}
////////////////////////////////////////////////////////////////////////////////
// Graph traversal
@ -454,12 +514,10 @@ func Equal(g1, g2 *Graph) bool {
// TODO Walk, but by edge
// TODO Walk, but without end. AKA FSM
// Walk will traverse the Graph, calling the callback on every Vertex in the
// Graph once. If startWith is non-nil then that Vertex will be the first one
// passed to the callback and used as the starting point of the traversal. If
// the callback returns false the traversal is stopped.
func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
// TODO figure out how to make Walk deterministic?
// Iter will iterate through the Graph's vertices, calling the callback on every
// Vertex in the Graph once. The vertex order used is non-deterministic. If the
// callback returns false the iteration is stopped.
func (g *Graph) Iter(callback func(*Vertex) bool) {
g.makeView()
if len(g.byVal) == 0 {
return
@ -482,12 +540,6 @@ func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
return true
}
if startWith != nil {
if !innerWalk(startWith) {
return
}
}
for _, v := range g.byVal {
if !innerWalk(v) {
return

View File

@ -1,6 +1,9 @@
package gg
import (
"fmt"
"sort"
"strings"
. "testing"
"github.com/stretchr/testify/assert"
@ -54,10 +57,10 @@ func assertVertexEqual(t *T, exp, got *Vertex, msgAndArgs ...interface{}) bool {
return assertInner(exp, got, map[*Vertex]bool{})
}
func assertWalk(t *T, expVals, expJuncs int, g *Graph, msgAndArgs ...interface{}) {
func assertIter(t *T, expVals, expJuncs int, g *Graph, msgAndArgs ...interface{}) {
seen := map[*Vertex]bool{}
var gotVals, gotJuncs int
g.Walk(nil, func(v *Vertex) bool {
g.Iter(func(v *Vertex) bool {
assert.NotContains(t, seen, v, msgAndArgs...)
seen[v] = true
if v.VertexType == ValueVertex {
@ -277,8 +280,8 @@ func TestGraph(t *T) {
// sanity check that graphs are equal to themselves
assert.True(t, Equal(out, out), msgAndArgs...)
// test the Walk method in here too
assertWalk(t, tests[i].numVals, tests[i].numJuncs, out, msgAndArgs...)
// test the Iter method in here too
assertIter(t, tests[i].numVals, tests[i].numJuncs, out, msgAndArgs...)
}
}
@ -407,6 +410,61 @@ func TestGraphDelValueIn(t *T) {
}
}
// deterministically hashes a Graph
func graphStr(g *Graph) string {
var vStr func(vertex) string
var oeStr func(OpenEdge) string
vStr = func(v vertex) string {
if v.VertexType == ValueVertex {
return fmt.Sprintf("v:%q\n", v.val.V.(string))
}
s := fmt.Sprintf("j:%d\n", len(v.in))
ssOE := make([]string, len(v.in))
for i := range v.in {
ssOE[i] = oeStr(v.in[i])
}
sort.Strings(ssOE)
return s + strings.Join(ssOE, "")
}
oeStr = func(oe OpenEdge) string {
s := fmt.Sprintf("oe:%q\n", oe.val.V.(string))
return s + vStr(oe.fromV)
}
sVV := make([]string, 0, len(g.vM))
for _, v := range g.vM {
sVV = append(sVV, vStr(v))
}
sort.Strings(sVV)
return strings.Join(sVV, "")
}
func assertEqualSets(t *T, exp, got []*Graph) bool {
if !assert.Equal(t, len(exp), len(got)) {
return false
}
m := map[*Graph]string{}
for _, g := range exp {
m[g] = graphStr(g)
}
for _, g := range got {
m[g] = graphStr(g)
}
sort.Slice(exp, func(i, j int) bool {
return m[exp[i]] < m[exp[j]]
})
sort.Slice(got, func(i, j int) bool {
return m[got[i]] < m[got[j]]
})
b := true
for i := range exp {
b = b || assert.True(t, Equal(exp[i], got[i]), "i:%d exp:%q got:%q", i, m[exp[i]], m[got[i]])
}
return b
}
func TestGraphUnion(t *T) {
assertUnion := func(g1, g2 *Graph) *Graph {
ga := g1.Union(g2)
@ -415,6 +473,11 @@ func TestGraphUnion(t *T) {
return ga
}
assertDisjoin := func(g *Graph, exp ...*Graph) {
ggDisj := g.Disjoin()
assertEqualSets(t, exp, ggDisj)
}
v0 := NewValue("v0")
v1 := NewValue("v1")
e0 := NewValue("e0")
@ -423,6 +486,8 @@ func TestGraphUnion(t *T) {
g := Null.AddValueIn(ValueOut(v0, e0), v1)
assert.True(t, Equal(g, assertUnion(g, Null)))
assertDisjoin(g, g)
}
v2 := NewValue("v2")
@ -436,6 +501,8 @@ func TestGraphUnion(t *T) {
assertVertexEqual(t, value(v1, edge(e0, value(v0))), g.ValueVertex(v1))
assertVertexEqual(t, value(v2), g.ValueVertex(v2))
assertVertexEqual(t, value(v3, edge(e1, value(v2))), g.ValueVertex(v3))
assertDisjoin(g, g0, g1)
}
va0, vb0 := NewValue("va0"), NewValue("vb0")
@ -470,6 +537,8 @@ func TestGraphUnion(t *T) {
edge(eb1, value(vb1)))),
g.ValueVertex(vb2),
)
assertDisjoin(g, ga, gb)
}
{ // Two partially overlapping graphs
@ -485,6 +554,8 @@ func TestGraphUnion(t *T) {
),
g.ValueVertex(v2),
)
assertDisjoin(g, g)
}
ej0 := NewValue("ej0")
@ -508,6 +579,8 @@ func TestGraphUnion(t *T) {
),
g.ValueVertex(v2),
)
assertDisjoin(g, g)
}
{ // Two equal graphs

View File

@ -172,7 +172,7 @@ func (jm jsonUnmarshaler) UnmarshalJSON(b []byte) error {
return vertex{}, err
}
}
return mkVertex(vJ.Type, val, ins), nil
return mkVertex(vJ.Type, val, ins...), nil
}
for _, v := range gJ.ValueVertices {

View File

@ -87,7 +87,7 @@ func (e *Engine) Solve() map[string]int {
// first the roots are determined to be the elements with no In edges, which
// _must_ exist since the graph presumably has no loops
var roots []*gg.Vertex
e.g.Walk(nil, func(v *gg.Vertex) bool {
e.g.Iter(func(v *gg.Vertex) bool {
if len(v.In) == 0 {
roots = append(roots, v)
m[vElem(v)] = 0