diff --git a/gg/gg.go b/gg/gg.go index 2d202c1..32438db 100644 --- a/gg/gg.go +++ b/gg/gg.go @@ -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 diff --git a/gg/gg_test.go b/gg/gg_test.go index 8b35f93..d022f77 100644 --- a/gg/gg_test.go +++ b/gg/gg_test.go @@ -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 diff --git a/gg/json.go b/gg/json.go index 5c7ae61..4357e9c 100644 --- a/gg/json.go +++ b/gg/json.go @@ -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 { diff --git a/gim/constraint/constraint.go b/gim/constraint/constraint.go index 7242350..13fd89c 100644 --- a/gim/constraint/constraint.go +++ b/gim/constraint/constraint.go @@ -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