gg: rename Walk to Iter, and implement Disjoin
This commit is contained in:
parent
efa7319555
commit
b906697151
120
gg/gg.go
120
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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user