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"
|
"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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user