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" "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 // 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. // 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. // You can create an instance manually as long as ID is globally unique.
@ -173,7 +157,7 @@ func (g *Graph) cp() *Graph {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Graph creation // 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} v := vertex{VertexType: typ, in: ins}
switch typ { switch typ {
case ValueVertex: 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 // multiple ValueOut OpenEdges constructed with the same val will be leaving the
// same Vertex instance in the constructed Graph. // same Vertex instance in the constructed Graph.
func ValueOut(val, edgeVal Value) OpenEdge { 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, // 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 // When constructing Graphs Junction vertices are de-duplicated on their input
// edges. So multiple Junction OpenEdges constructed with the same set of 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. // 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{ return OpenEdge{
fromV: mkVertex(JunctionVertex, Value{}, in), fromV: mkVertex(JunctionVertex, Value{}, ins...),
val: edgeVal, 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 // referenced within toe OpenEdge which do not yet exist in the Graph will also
// be created in this step. // be created in this step.
func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph { func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
to := mkVertex(ValueVertex, val, nil) to := mkVertex(ValueVertex, val)
toID := to.id toID := to.id
// if to is already in the graph, pull it out, as it might have existing in // 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 // 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. // which that edge is their only outgoing edge will be removed from the Graph.
func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph { func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
to := mkVertex(ValueVertex, val, nil) to := mkVertex(ValueVertex, val)
toID := to.id toID := to.id
// pull to out of the graph. if it's not there then bail // 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 // 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 // two. Value vertices which are shared between the two will be merged so that
// the new vertex has the input edges of both. // 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 { func (g *Graph) Union(g2 *Graph) *Graph {
g = g.cp() g = g.cp()
for vID, v2 := range g2.vM { for vID, v2 := range g2.vM {
@ -361,6 +347,80 @@ func (g *Graph) Union(g2 *Graph) *Graph {
return g 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 // Graph traversal
@ -454,12 +514,10 @@ func Equal(g1, g2 *Graph) bool {
// TODO Walk, but by edge // TODO Walk, but by edge
// TODO Walk, but without end. AKA FSM // TODO Walk, but without end. AKA FSM
// Walk will traverse the Graph, calling the callback on every Vertex in the // Iter will iterate through the Graph's vertices, calling the callback on every
// Graph once. If startWith is non-nil then that Vertex will be the first one // Vertex in the Graph once. The vertex order used is non-deterministic. If the
// passed to the callback and used as the starting point of the traversal. If // callback returns false the iteration is stopped.
// the callback returns false the traversal is stopped. func (g *Graph) Iter(callback func(*Vertex) bool) {
func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
// TODO figure out how to make Walk deterministic?
g.makeView() g.makeView()
if len(g.byVal) == 0 { if len(g.byVal) == 0 {
return return
@ -482,12 +540,6 @@ func (g *Graph) Walk(startWith *Vertex, callback func(*Vertex) bool) {
return true return true
} }
if startWith != nil {
if !innerWalk(startWith) {
return
}
}
for _, v := range g.byVal { for _, v := range g.byVal {
if !innerWalk(v) { if !innerWalk(v) {
return return

View File

@ -1,6 +1,9 @@
package gg package gg
import ( import (
"fmt"
"sort"
"strings"
. "testing" . "testing"
"github.com/stretchr/testify/assert" "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{}) 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{} seen := map[*Vertex]bool{}
var gotVals, gotJuncs int var gotVals, gotJuncs int
g.Walk(nil, func(v *Vertex) bool { g.Iter(func(v *Vertex) bool {
assert.NotContains(t, seen, v, msgAndArgs...) assert.NotContains(t, seen, v, msgAndArgs...)
seen[v] = true seen[v] = true
if v.VertexType == ValueVertex { if v.VertexType == ValueVertex {
@ -277,8 +280,8 @@ func TestGraph(t *T) {
// sanity check that graphs are equal to themselves // sanity check that graphs are equal to themselves
assert.True(t, Equal(out, out), msgAndArgs...) assert.True(t, Equal(out, out), msgAndArgs...)
// test the Walk method in here too // test the Iter method in here too
assertWalk(t, tests[i].numVals, tests[i].numJuncs, out, msgAndArgs...) 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) { func TestGraphUnion(t *T) {
assertUnion := func(g1, g2 *Graph) *Graph { assertUnion := func(g1, g2 *Graph) *Graph {
ga := g1.Union(g2) ga := g1.Union(g2)
@ -415,6 +473,11 @@ func TestGraphUnion(t *T) {
return ga return ga
} }
assertDisjoin := func(g *Graph, exp ...*Graph) {
ggDisj := g.Disjoin()
assertEqualSets(t, exp, ggDisj)
}
v0 := NewValue("v0") v0 := NewValue("v0")
v1 := NewValue("v1") v1 := NewValue("v1")
e0 := NewValue("e0") e0 := NewValue("e0")
@ -423,6 +486,8 @@ func TestGraphUnion(t *T) {
g := Null.AddValueIn(ValueOut(v0, e0), v1) g := Null.AddValueIn(ValueOut(v0, e0), v1)
assert.True(t, Equal(g, assertUnion(g, Null))) assert.True(t, Equal(g, assertUnion(g, Null)))
assertDisjoin(g, g)
} }
v2 := NewValue("v2") 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(v1, edge(e0, value(v0))), g.ValueVertex(v1))
assertVertexEqual(t, value(v2), g.ValueVertex(v2)) assertVertexEqual(t, value(v2), g.ValueVertex(v2))
assertVertexEqual(t, value(v3, edge(e1, value(v2))), g.ValueVertex(v3)) assertVertexEqual(t, value(v3, edge(e1, value(v2))), g.ValueVertex(v3))
assertDisjoin(g, g0, g1)
} }
va0, vb0 := NewValue("va0"), NewValue("vb0") va0, vb0 := NewValue("va0"), NewValue("vb0")
@ -470,6 +537,8 @@ func TestGraphUnion(t *T) {
edge(eb1, value(vb1)))), edge(eb1, value(vb1)))),
g.ValueVertex(vb2), g.ValueVertex(vb2),
) )
assertDisjoin(g, ga, gb)
} }
{ // Two partially overlapping graphs { // Two partially overlapping graphs
@ -485,6 +554,8 @@ func TestGraphUnion(t *T) {
), ),
g.ValueVertex(v2), g.ValueVertex(v2),
) )
assertDisjoin(g, g)
} }
ej0 := NewValue("ej0") ej0 := NewValue("ej0")
@ -508,6 +579,8 @@ func TestGraphUnion(t *T) {
), ),
g.ValueVertex(v2), g.ValueVertex(v2),
) )
assertDisjoin(g, g)
} }
{ // Two equal graphs { // Two equal graphs

View File

@ -172,7 +172,7 @@ func (jm jsonUnmarshaler) UnmarshalJSON(b []byte) error {
return vertex{}, err return vertex{}, err
} }
} }
return mkVertex(vJ.Type, val, ins), nil return mkVertex(vJ.Type, val, ins...), nil
} }
for _, v := range gJ.ValueVertices { 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 // first the roots are determined to be the elements with no In edges, which
// _must_ exist since the graph presumably has no loops // _must_ exist since the graph presumably has no loops
var roots []*gg.Vertex 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 { if len(v.In) == 0 {
roots = append(roots, v) roots = append(roots, v)
m[vElem(v)] = 0 m[vElem(v)] = 0