refacctor gg to use Value instead of Identifier, which will make serializing more straightforward, and reduces some complexity of the code besides

This commit is contained in:
Brian Picciano 2018-01-21 15:39:25 +00:00
parent 754b75407a
commit e52befb7ed
6 changed files with 416 additions and 364 deletions

181
gg/gg.go
View File

@ -2,63 +2,62 @@
package gg package gg
import ( import (
"crypto/md5" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"hash" "strings"
) )
// TODO instead of Identifier being public, make it encoding.TextMarshaler // 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.
// Identifier is implemented by any value which can return a unique string for // You can create an instance manually as long as ID is globally unique.
// itself via an Identify method type Value struct {
type Identifier interface { ID string
Identify(hash.Hash) V interface{}
} }
func identify(i Identifier) string { // NewValue returns a Value instance wrapping any go value. The Value returned
h := md5.New() // will be independent of the passed in go value. So if the same go value is
i.Identify(h) // passed in twice then the two returned Value instances will be treated as
return hex.EncodeToString(h.Sum(nil)) // being different values by Graph.
func NewValue(V interface{}) Value {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return Value{
ID: hex.EncodeToString(b),
V: V,
} }
// Str is an Identifier identified by its string value
type Str string
// Identify implements the Identifier interface
func (s Str) Identify(h hash.Hash) {
fmt.Fprintf(h, "%q", s)
} }
////////////////////////////////////////////////////////////////////////////////
// VertexType enumerates the different possible vertex types // VertexType enumerates the different possible vertex types
type VertexType string type VertexType string
const ( const (
// Value is a Vertex which contains exactly one value and has at least one // ValueVertex is a Vertex which contains exactly one value and has at least
// edge (either input or output) // one edge (either input or output)
Value VertexType = "value" ValueVertex VertexType = "value"
// Junction is a Vertex which contains two or more in edges and exactly one // JunctionVertex is a Vertex which contains two or more in edges and
// out edge // exactly one out edge
Junction VertexType = "junction" JunctionVertex VertexType = "junction"
) )
// Edge is a uni-directional connection between two vertices with an attribute // Edge is a uni-directional connection between two vertices with an attribute
// value // value
type Edge struct { type Edge struct {
From *Vertex From *Vertex
Value Identifier Value Value
To *Vertex To *Vertex
} }
// Vertex is a vertex in a Graph. No fields should be modified directly, only // Vertex is a vertex in a Graph. No fields should be modified directly, only
// through method calls // through method calls
type Vertex struct { type Vertex struct {
ID string
VertexType VertexType
ID string // identifier of the vertex, unique within the graph Value Value // Value is valid if-and-only-if VertexType is ValueVertex
Value Identifier // Value is valid if-and-only-if VertexType is Value
In, Out []Edge In, Out []Edge
} }
@ -72,14 +71,11 @@ type OpenEdge struct {
// already that will need to be taken into account when persisting into the // already that will need to be taken into account when persisting into the
// graph // graph
fromV vertex fromV vertex
val Identifier val Value
} }
// Identify implements the Identifier interface func (oe OpenEdge) id() string {
func (oe OpenEdge) Identify(h hash.Hash) { return fmt.Sprintf("(%s,%s)", oe.fromV.id, oe.val.ID)
fmt.Fprintln(h, "openEdge")
oe.fromV.Identify(h)
oe.val.Identify(h)
} }
// vertex is a representation of a vertex in the graph. Each Graph contains a // vertex is a representation of a vertex in the graph. Each Graph contains a
@ -95,28 +91,12 @@ func (oe OpenEdge) Identify(h hash.Hash) {
// When a view is constructed in makeView these Value instances are deduplicated // When a view is constructed in makeView these Value instances are deduplicated
// and the top-level one's in value is used to properly connect it. // and the top-level one's in value is used to properly connect it.
type vertex struct { type vertex struct {
id string
VertexType VertexType
val Identifier val Value
in []OpenEdge in []OpenEdge
} }
// A Value vertex is unique by the value it contains
// A Junction vertex is unique by its input edges
func (v vertex) Identify(h hash.Hash) {
switch v.VertexType {
case Value:
fmt.Fprintln(h, "value")
v.val.Identify(h)
case Junction:
fmt.Fprintf(h, "junction:%d\n", len(v.in))
for _, in := range v.in {
in.Identify(h)
}
default:
panic(fmt.Sprintf("invalid VertexType:%#v", v))
}
}
func (v vertex) cp() vertex { func (v vertex) cp() vertex {
cp := v cp := v
cp.in = make([]OpenEdge, len(v.in)) cp.in = make([]OpenEdge, len(v.in))
@ -125,9 +105,9 @@ func (v vertex) cp() vertex {
} }
func (v vertex) hasOpenEdge(oe OpenEdge) bool { func (v vertex) hasOpenEdge(oe OpenEdge) bool {
oeID := identify(oe) oeID := oe.id()
for _, in := range v.in { for _, in := range v.in {
if identify(in) == oeID { if in.id() == oeID {
return true return true
} }
} }
@ -135,9 +115,9 @@ func (v vertex) hasOpenEdge(oe OpenEdge) bool {
} }
func (v vertex) cpAndDelOpenEdge(oe OpenEdge) (vertex, bool) { func (v vertex) cpAndDelOpenEdge(oe OpenEdge) (vertex, bool) {
oeID := identify(oe) oeID := oe.id()
for i, in := range v.in { for i, in := range v.in {
if identify(in) == oeID { if in.id() == oeID {
v = v.cp() v = v.cp()
v.in = append(v.in[:i], v.in[i+1:]...) v.in = append(v.in[:i], v.in[i+1:]...)
return v, true return v, true
@ -168,8 +148,8 @@ func (g *Graph) cp() *Graph {
cp := &Graph{ cp := &Graph{
vM: make(map[string]vertex, len(g.vM)), vM: make(map[string]vertex, len(g.vM)),
} }
for id, v := range g.vM { for vID, v := range g.vM {
cp.vM[id] = v cp.vM[vID] = v
} }
return cp return cp
} }
@ -178,16 +158,17 @@ func (g *Graph) cp() *Graph {
// Graph creation // Graph creation
// ValueOut creates a OpenEdge which, when used to construct a Graph, represents // ValueOut creates a OpenEdge which, when used to construct a Graph, represents
// an edge (with edgeVal attached to it) leaving the Value Vertex containing // an edge (with edgeVal attached to it) coming from the ValueVertex containing
// val. // val.
// //
// When constructing Graphs Value vertices are de-duplicated on their value. So // When constructing Graphs, Value vertices are de-duplicated on their Value. So
// 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 Identifier) OpenEdge { func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{ return OpenEdge{
fromV: vertex{ fromV: vertex{
VertexType: Value, id: val.ID,
VertexType: ValueVertex,
val: val, val: val,
}, },
val: edgeVal, val: edgeVal,
@ -195,16 +176,21 @@ func ValueOut(val, edgeVal Identifier) OpenEdge {
} }
// JunctionOut creates a OpenEdge which, when used to construct a Graph, // JunctionOut creates a OpenEdge which, when used to construct a Graph,
// represents an edge (with edgeVal attached to it) leaving the Junction Vertex // represents an edge (with edgeVal attached to it) coming from the
// comprised of the given ordered-set of input edges. // JunctionVertex comprised of the given ordered-set of input edges.
// //
// 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 Identifier) OpenEdge { func JunctionOut(in []OpenEdge, edgeVal Value) OpenEdge {
inIDs := make([]string, len(in))
for i := range in {
inIDs[i] = in[i].id()
}
return OpenEdge{ return OpenEdge{
fromV: vertex{ fromV: vertex{
VertexType: Junction, id: "[" + strings.Join(inIDs, ",") + "]",
VertexType: JunctionVertex,
in: in, in: in,
}, },
val: edgeVal, val: edgeVal,
@ -215,12 +201,13 @@ func JunctionOut(in []OpenEdge, edgeVal Identifier) OpenEdge {
// val, returning the new Graph which reflects that connection. Any Vertices // val, returning the new Graph which reflects that connection. Any Vertices
// 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 Identifier) *Graph { func (g *Graph) AddValueIn(oe OpenEdge, val Value) *Graph {
to := vertex{ to := vertex{
VertexType: Value, id: val.ID,
VertexType: ValueVertex,
val: val, val: val,
} }
toID := identify(to) 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
// edges we want to keep // edges we want to keep
@ -241,8 +228,8 @@ func (g *Graph) AddValueIn(oe OpenEdge, val Identifier) *Graph {
// recursively add in any vertices which aren't already there // recursively add in any vertices which aren't already there
var persist func(vertex) var persist func(vertex)
persist = func(v vertex) { persist = func(v vertex) {
vID := identify(v) if v.VertexType == ValueVertex {
if v.VertexType == Value { vID := v.id
if _, ok := g.vM[vID]; !ok { if _, ok := g.vM[vID]; !ok {
g.vM[vID] = v g.vM[vID] = v
} }
@ -266,12 +253,13 @@ func (g *Graph) AddValueIn(oe OpenEdge, val Identifier) *Graph {
// the Value Vertex doesn't exist within the graph, or it doesn't have the given // the Value Vertex doesn't exist within the graph, or it doesn't have the given
// 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 Identifier) *Graph { func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
to := vertex{ to := vertex{
VertexType: Value, id: val.ID,
VertexType: ValueVertex,
val: val, val: val,
} }
toID := identify(to) 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
var ok bool var ok bool
@ -293,9 +281,9 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph {
var connectedTo func(string, vertex) bool var connectedTo func(string, vertex) bool
connectedTo = func(vID string, curr vertex) bool { connectedTo = func(vID string, curr vertex) bool {
for _, in := range curr.in { for _, in := range curr.in {
if in.fromV.VertexType == Value && identify(in.fromV) == vID { if in.fromV.VertexType == ValueVertex && in.fromV.id == vID {
return true return true
} else if in.fromV.VertexType == Junction && connectedTo(vID, in.fromV) { } else if in.fromV.VertexType == JunctionVertex && connectedTo(vID, in.fromV) {
return true return true
} }
} }
@ -305,7 +293,7 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph {
// isOrphaned returns whether the given vertex has any connections to other // isOrphaned returns whether the given vertex has any connections to other
// nodes in the graph // nodes in the graph
isOrphaned := func(v vertex) bool { isOrphaned := func(v vertex) bool {
vID := identify(v) vID := v.id
if v, ok := g.vM[vID]; ok && len(v.in) > 0 { if v, ok := g.vM[vID]; ok && len(v.in) > 0 {
return false return false
} }
@ -328,11 +316,11 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph {
// Vertices referenced in it which are now orphaned // Vertices referenced in it which are now orphaned
var rmOrphaned func(OpenEdge) var rmOrphaned func(OpenEdge)
rmOrphaned = func(oe OpenEdge) { rmOrphaned = func(oe OpenEdge) {
if oe.fromV.VertexType == Value && isOrphaned(oe.fromV) { if oe.fromV.VertexType == ValueVertex && isOrphaned(oe.fromV) {
delete(g.vM, identify(oe.fromV)) delete(g.vM, oe.fromV.id)
} else if oe.fromV.VertexType == Junction { } else if oe.fromV.VertexType == JunctionVertex {
for _, juncHe := range oe.fromV.in { for _, juncOe := range oe.fromV.in {
rmOrphaned(juncHe) rmOrphaned(juncOe)
} }
} }
} }
@ -377,18 +365,17 @@ func (g *Graph) makeView() {
var getV func(vertex, bool) *Vertex var getV func(vertex, bool) *Vertex
getV = func(v vertex, top bool) *Vertex { getV = func(v vertex, top bool) *Vertex {
vID := identify(v) V, ok := g.all[v.id]
V, ok := g.all[vID]
if !ok { if !ok {
V = &Vertex{VertexType: v.VertexType, ID: vID, Value: v.val} V = &Vertex{ID: v.id, VertexType: v.VertexType, Value: v.val}
g.all[vID] = V g.all[v.id] = V
} }
// we can be sure all Value vertices will be called with top==true at // we can be sure all Value vertices will be called with top==true at
// some point, so we only need to descend into the input edges if: // some point, so we only need to descend into the input edges if:
// * top is true // * top is true
// * this is a junction's first time being gotten // * this is a junction's first time being gotten
if !top && (ok || v.VertexType != Junction) { if !top && (ok || v.VertexType != JunctionVertex) {
return V return V
} }
@ -400,8 +387,8 @@ func (g *Graph) makeView() {
V.In = append(V.In, e) V.In = append(V.In, e)
} }
if v.VertexType == Value { if v.VertexType == ValueVertex {
g.byVal[identify(v.val)] = V g.byVal[v.val.ID] = V
} }
return V return V
@ -412,15 +399,15 @@ func (g *Graph) makeView() {
} }
} }
// Value returns the Value Vertex for the given value. If the Graph doesn't // ValueVertex returns the Value Vertex for the given value. If the Graph
// contain a vertex for the value then nil is returned // doesn't contain a vertex for the value then nil is returned
func (g *Graph) Value(val Identifier) *Vertex { func (g *Graph) ValueVertex(val Value) *Vertex {
g.makeView() g.makeView()
return g.byVal[identify(val)] return g.byVal[val.ID]
} }
// Values returns all Value Vertices in the Graph // ValueVertices returns all Value Vertices in the Graph
func (g *Graph) Values() []*Vertex { func (g *Graph) ValueVertices() []*Vertex {
g.makeView() g.makeView()
vv := make([]*Vertex, 0, len(g.byVal)) vv := make([]*Vertex, 0, len(g.byVal))
for _, v := range g.byVal { for _, v := range g.byVal {

View File

@ -1,44 +1,30 @@
package gg package gg
import ( import (
"fmt"
"hash"
. "testing" . "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type idAny struct { func edge(val Value, from *Vertex) Edge {
i interface{} return Edge{Value: val, From: from}
} }
func (i idAny) Identify(h hash.Hash) { func value(val Value, in ...Edge) *Vertex {
fmt.Fprintln(h, i)
}
func id(i interface{}) Identifier {
return idAny{i: i}
}
func edge(val string, from *Vertex) Edge {
return Edge{Value: id(val), From: from}
}
func value(val string, in ...Edge) *Vertex {
return &Vertex{ return &Vertex{
VertexType: Value, VertexType: ValueVertex,
Value: id(val), Value: val,
In: in, In: in,
} }
} }
func junction(val string, in ...Edge) Edge { func junction(val Value, in ...Edge) Edge {
return Edge{ return Edge{
From: &Vertex{ From: &Vertex{
VertexType: Junction, VertexType: JunctionVertex,
In: in, In: in,
}, },
Value: id(val), Value: val,
} }
} }
@ -74,7 +60,7 @@ func assertWalk(t *T, expVals, expJuncs int, g *Graph, msgAndArgs ...interface{}
g.Walk(nil, func(v *Vertex) bool { g.Walk(nil, 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 == Value { if v.VertexType == ValueVertex {
gotVals++ gotVals++
} else { } else {
gotJuncs++ gotJuncs++
@ -102,122 +88,140 @@ func mkTest(name string, out func() *Graph, numVals, numJuncs int, exp ...*Verte
} }
func TestGraph(t *T) { func TestGraph(t *T) {
var (
v0 = NewValue("v0")
v1 = NewValue("v1")
v2 = NewValue("v2")
v3 = NewValue("v3")
e0 = NewValue("e0")
e00 = NewValue("e00")
e01 = NewValue("e01")
e1 = NewValue("e1")
e10 = NewValue("e10")
e11 = NewValue("e11")
e2 = NewValue("e2")
e20 = NewValue("e20")
e21 = NewValue("e21")
ej0 = NewValue("ej0")
ej1 = NewValue("ej1")
ej2 = NewValue("ej2")
)
tests := []graphTest{ tests := []graphTest{
mkTest( mkTest(
"values-basic", "values-basic",
func() *Graph { func() *Graph {
return Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) return Null.AddValueIn(ValueOut(v0, e0), v1)
}, },
2, 0, 2, 0,
value("v0"), value(v0),
value("v1", edge("e0", value("v0"))), value(v1, edge(e0, value(v0))),
), ),
mkTest( mkTest(
"values-2edges", "values-2edges",
func() *Graph { func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) g0 := Null.AddValueIn(ValueOut(v0, e0), v2)
return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) return g0.AddValueIn(ValueOut(v1, e1), v2)
}, },
3, 0, 3, 0,
value("v0"), value(v0),
value("v1"), value(v1),
value("v2", value(v2,
edge("e0", value("v0")), edge(e0, value(v0)),
edge("e1", value("v1")), edge(e1, value(v1)),
), ),
), ),
mkTest( mkTest(
"values-separate", "values-separate",
func() *Graph { func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
return g0.AddValueIn(ValueOut(id("v2"), id("e2")), id("v3")) return g0.AddValueIn(ValueOut(v2, e2), v3)
}, },
4, 0, 4, 0,
value("v0"), value(v0),
value("v1", edge("e0", value("v0"))), value(v1, edge(e0, value(v0))),
value("v2"), value(v2),
value("v3", edge("e2", value("v2"))), value(v3, edge(e2, value(v2))),
), ),
mkTest( mkTest(
"values-circular", "values-circular",
func() *Graph { func() *Graph {
return Null.AddValueIn(ValueOut(id("v0"), id("e")), id("v0")) return Null.AddValueIn(ValueOut(v0, e0), v0)
}, },
1, 0, 1, 0,
value("v0", edge("e", value("v0"))), value(v0, edge(e0, value(v0))),
), ),
mkTest( mkTest(
"values-circular2", "values-circular2",
func() *Graph { func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v0")) return g0.AddValueIn(ValueOut(v1, e1), v0)
}, },
2, 0, 2, 0,
value("v0", edge("e1", value("v1", edge("e0", value("v0"))))), value(v0, edge(e1, value(v1, edge(e0, value(v0))))),
value("v1", edge("e0", value("v0", edge("e1", value("v1"))))), value(v1, edge(e0, value(v0, edge(e1, value(v1))))),
), ),
mkTest( mkTest(
"values-circular3", "values-circular3",
func() *Graph { func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g1 := g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) g1 := g0.AddValueIn(ValueOut(v1, e1), v2)
return g1.AddValueIn(ValueOut(id("v2"), id("e2")), id("v1")) return g1.AddValueIn(ValueOut(v2, e2), v1)
}, },
3, 0, 3, 0,
value("v0"), value(v0),
value("v1", value(v1,
edge("e0", value("v0")), edge(e0, value(v0)),
edge("e2", value("v2", edge("e1", value("v1")))), edge(e2, value(v2, edge(e1, value(v1)))),
), ),
value("v2", edge("e1", value("v1", value(v2, edge(e1, value(v1,
edge("e0", value("v0")), edge(e0, value(v0)),
edge("e2", value("v2")), edge(e2, value(v2)),
))), ))),
), ),
mkTest( mkTest(
"junction-basic", "junction-basic",
func() *Graph { func() *Graph {
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
e1 := ValueOut(id("v1"), id("e1")) e1 := ValueOut(v1, e1)
ej0 := JunctionOut([]OpenEdge{e0, e1}, id("ej0")) ej0 := JunctionOut([]OpenEdge{e0, e1}, ej0)
return Null.AddValueIn(ej0, id("v2")) return Null.AddValueIn(ej0, v2)
}, },
3, 1, 3, 1,
value("v0"), value("v1"), value(v0), value(v1),
value("v2", junction("ej0", value(v2, junction(ej0,
edge("e0", value("v0")), edge(e0, value(v0)),
edge("e1", value("v1")), edge(e1, value(v1)),
)), )),
), ),
mkTest( mkTest(
"junction-basic2", "junction-basic2",
func() *Graph { func() *Graph {
e00 := ValueOut(id("v0"), id("e00")) e00 := ValueOut(v0, e00)
e10 := ValueOut(id("v1"), id("e10")) e10 := ValueOut(v1, e10)
ej0 := JunctionOut([]OpenEdge{e00, e10}, id("ej0")) ej0 := JunctionOut([]OpenEdge{e00, e10}, ej0)
e01 := ValueOut(id("v0"), id("e01")) e01 := ValueOut(v0, e01)
e11 := ValueOut(id("v1"), id("e11")) e11 := ValueOut(v1, e11)
ej1 := JunctionOut([]OpenEdge{e01, e11}, id("ej1")) ej1 := JunctionOut([]OpenEdge{e01, e11}, ej1)
ej2 := JunctionOut([]OpenEdge{ej0, ej1}, id("ej2")) ej2 := JunctionOut([]OpenEdge{ej0, ej1}, ej2)
return Null.AddValueIn(ej2, id("v2")) return Null.AddValueIn(ej2, v2)
}, },
3, 3, 3, 3,
value("v0"), value("v1"), value(v0), value(v1),
value("v2", junction("ej2", value(v2, junction(ej2,
junction("ej0", junction(ej0,
edge("e00", value("v0")), edge(e00, value(v0)),
edge("e10", value("v1")), edge(e10, value(v1)),
), ),
junction("ej1", junction(ej1,
edge("e01", value("v0")), edge(e01, value(v0)),
edge("e11", value("v1")), edge(e11, value(v1)),
), ),
)), )),
), ),
@ -225,27 +229,27 @@ func TestGraph(t *T) {
mkTest( mkTest(
"junction-circular", "junction-circular",
func() *Graph { func() *Graph {
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
e1 := ValueOut(id("v1"), id("e1")) e1 := ValueOut(v1, e1)
ej0 := JunctionOut([]OpenEdge{e0, e1}, id("ej0")) ej0 := JunctionOut([]OpenEdge{e0, e1}, ej0)
g0 := Null.AddValueIn(ej0, id("v2")) g0 := Null.AddValueIn(ej0, v2)
e20 := ValueOut(id("v2"), id("e20")) e20 := ValueOut(v2, e20)
g1 := g0.AddValueIn(e20, id("v0")) g1 := g0.AddValueIn(e20, v0)
e21 := ValueOut(id("v2"), id("e21")) e21 := ValueOut(v2, e21)
return g1.AddValueIn(e21, id("v1")) return g1.AddValueIn(e21, v1)
}, },
3, 1, 3, 1,
value("v0", edge("e20", value("v2", junction("ej0", value(v0, edge(e20, value(v2, junction(ej0,
edge("e0", value("v0")), edge(e0, value(v0)),
edge("e1", value("v1", edge("e21", value("v2")))), edge(e1, value(v1, edge(e21, value(v2)))),
)))), )))),
value("v1", edge("e21", value("v2", junction("ej0", value(v1, edge(e21, value(v2, junction(ej0,
edge("e0", value("v0", edge("e20", value("v2")))), edge(e0, value(v0, edge(e20, value(v2)))),
edge("e1", value("v1")), edge(e1, value(v1)),
)))), )))),
value("v2", junction("ej0", value(v2, junction(ej0,
edge("e0", value("v0", edge("e20", value("v2")))), edge(e0, value(v0, edge(e20, value(v2)))),
edge("e1", value("v1", edge("e21", value("v2")))), edge(e1, value(v1, edge(e21, value(v2)))),
)), )),
), ),
} }
@ -256,9 +260,9 @@ func TestGraph(t *T) {
for j, exp := range tests[i].exp { for j, exp := range tests[i].exp {
msgAndArgs := []interface{}{ msgAndArgs := []interface{}{
"tests[%d].name:%q exp[%d].val:%q", "tests[%d].name:%q exp[%d].val:%q",
i, tests[i].name, j, exp.Value.(idAny).i, i, tests[i].name, j, exp.Value.V.(string),
} }
v := out.Value(exp.Value) v := out.ValueVertex(exp.Value)
if !assert.NotNil(t, v, msgAndArgs...) { if !assert.NotNil(t, v, msgAndArgs...) {
continue continue
} }
@ -279,109 +283,127 @@ func TestGraph(t *T) {
} }
func TestGraphImmutability(t *T) { func TestGraphImmutability(t *T) {
e0 := ValueOut(id("v0"), id("e0")) v0 := NewValue("v0")
g0 := Null.AddValueIn(e0, id("v1")) v1 := NewValue("v1")
assert.Nil(t, Null.Value(id("v0"))) e0 := NewValue("e0")
assert.Nil(t, Null.Value(id("v1"))) oe0 := ValueOut(v0, e0)
assert.NotNil(t, g0.Value(id("v0"))) g0 := Null.AddValueIn(oe0, v1)
assert.NotNil(t, g0.Value(id("v1"))) assert.Nil(t, Null.ValueVertex(v0))
assert.Nil(t, Null.ValueVertex(v1))
assert.NotNil(t, g0.ValueVertex(v0))
assert.NotNil(t, g0.ValueVertex(v1))
// half-edges should be re-usable // half-edges should be re-usable
e1 := ValueOut(id("v2"), id("e1")) v2 := NewValue("v2")
g1a := g0.AddValueIn(e1, id("v3a")) v3a, v3b := NewValue("v3a"), NewValue("v3b")
g1b := g0.AddValueIn(e1, id("v3b")) e1 := NewValue("e1")
assertVertexEqual(t, value("v3a", edge("e1", value("v2"))), g1a.Value(id("v3a"))) oe1 := ValueOut(v2, e1)
assert.Nil(t, g1a.Value(id("v3b"))) g1a := g0.AddValueIn(oe1, v3a)
assertVertexEqual(t, value("v3b", edge("e1", value("v2"))), g1b.Value(id("v3b"))) g1b := g0.AddValueIn(oe1, v3b)
assert.Nil(t, g1b.Value(id("v3a"))) assertVertexEqual(t, value(v3a, edge(e1, value(v2))), g1a.ValueVertex(v3a))
assert.Nil(t, g1a.ValueVertex(v3b))
assertVertexEqual(t, value(v3b, edge(e1, value(v2))), g1b.ValueVertex(v3b))
assert.Nil(t, g1b.ValueVertex(v3a))
// ... even re-usable twice in succession // ... even re-usable twice in succession
g2 := g0.AddValueIn(e1, id("v3")).AddValueIn(e1, id("v4")) v3 := NewValue("v3")
assert.Nil(t, g2.Value(id("v3b"))) v4 := NewValue("v4")
assert.Nil(t, g2.Value(id("v3a"))) g2 := g0.AddValueIn(oe1, v3).AddValueIn(oe1, v4)
assertVertexEqual(t, value("v3", edge("e1", value("v2"))), g2.Value(id("v3"))) assert.Nil(t, g2.ValueVertex(v3b))
assertVertexEqual(t, value("v4", edge("e1", value("v2"))), g2.Value(id("v4"))) assert.Nil(t, g2.ValueVertex(v3a))
assertVertexEqual(t, value(v3, edge(e1, value(v2))), g2.ValueVertex(v3))
assertVertexEqual(t, value(v4, edge(e1, value(v2))), g2.ValueVertex(v4))
} }
func TestGraphDelValueIn(t *T) { func TestGraphDelValueIn(t *T) {
v0 := NewValue("v0")
v1 := NewValue("v1")
e0 := NewValue("e0")
{ // removing from null { // removing from null
g := Null.DelValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g := Null.DelValueIn(ValueOut(v0, e0), v1)
assert.True(t, Equal(Null, g)) assert.True(t, Equal(Null, g))
} }
e1 := NewValue("e1")
{ // removing edge from vertex which doesn't have that edge { // removing edge from vertex which doesn't have that edge
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g1 := g0.DelValueIn(ValueOut(id("v0"), id("e1")), id("v1")) g1 := g0.DelValueIn(ValueOut(v0, e1), v1)
assert.True(t, Equal(g0, g1)) assert.True(t, Equal(g0, g1))
} }
{ // removing only edge { // removing only edge
oe := ValueOut(id("v0"), id("e0")) oe := ValueOut(v0, e0)
g0 := Null.AddValueIn(oe, id("v1")) g0 := Null.AddValueIn(oe, v1)
g1 := g0.DelValueIn(oe, id("v1")) g1 := g0.DelValueIn(oe, v1)
assert.True(t, Equal(Null, g1)) assert.True(t, Equal(Null, g1))
} }
ej0 := NewValue("ej0")
v2 := NewValue("v2")
{ // removing only edge (junction) { // removing only edge (junction)
oe := JunctionOut([]OpenEdge{ oe := JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")), ValueOut(v0, e0),
ValueOut(id("v1"), id("e1")), ValueOut(v1, e1),
}, id("ej0")) }, ej0)
g0 := Null.AddValueIn(oe, id("v2")) g0 := Null.AddValueIn(oe, v2)
g1 := g0.DelValueIn(oe, id("v2")) g1 := g0.DelValueIn(oe, v2)
assert.True(t, Equal(Null, g1)) assert.True(t, Equal(Null, g1))
} }
{ // removing one of two edges { // removing one of two edges
oe := ValueOut(id("v1"), id("e0")) oe := ValueOut(v1, e0)
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) g0 := Null.AddValueIn(ValueOut(v0, e0), v2)
g1 := g0.AddValueIn(oe, id("v2")) g1 := g0.AddValueIn(oe, v2)
g2 := g1.DelValueIn(oe, id("v2")) g2 := g1.DelValueIn(oe, v2)
assert.True(t, Equal(g0, g2)) assert.True(t, Equal(g0, g2))
assert.NotNil(t, g2.Value(id("v0"))) assert.NotNil(t, g2.ValueVertex(v0))
assert.Nil(t, g2.Value(id("v1"))) assert.Nil(t, g2.ValueVertex(v1))
assert.NotNil(t, g2.Value(id("v2"))) assert.NotNil(t, g2.ValueVertex(v2))
} }
e2 := NewValue("e2")
eja, ejb := NewValue("eja"), NewValue("ejb")
v3 := NewValue("v3")
{ // removing one of two edges (junction) { // removing one of two edges (junction)
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
e1 := ValueOut(id("v1"), id("e1")) e1 := ValueOut(v1, e1)
e2 := ValueOut(id("v2"), id("e2")) e2 := ValueOut(v2, e2)
oeA := JunctionOut([]OpenEdge{e0, e1}, id("oeA")) oeA := JunctionOut([]OpenEdge{e0, e1}, eja)
oeB := JunctionOut([]OpenEdge{e1, e2}, id("oeB")) oeB := JunctionOut([]OpenEdge{e1, e2}, ejb)
g0a := Null.AddValueIn(oeA, id("v3")) g0a := Null.AddValueIn(oeA, v3)
g0b := Null.AddValueIn(oeB, id("v3")) g0b := Null.AddValueIn(oeB, v3)
g1 := g0a.Union(g0b).DelValueIn(oeA, id("v3")) g1 := g0a.Union(g0b).DelValueIn(oeA, v3)
assert.True(t, Equal(g1, g0b)) assert.True(t, Equal(g1, g0b))
assert.Nil(t, g1.Value(id("v0"))) assert.Nil(t, g1.ValueVertex(v0))
assert.NotNil(t, g1.Value(id("v1"))) assert.NotNil(t, g1.ValueVertex(v1))
assert.NotNil(t, g1.Value(id("v2"))) assert.NotNil(t, g1.ValueVertex(v2))
assert.NotNil(t, g1.Value(id("v3"))) assert.NotNil(t, g1.ValueVertex(v3))
} }
{ // removing one of two edges in circular graph { // removing one of two edges in circular graph
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
e1 := ValueOut(id("v1"), id("e1")) e1 := ValueOut(v1, e1)
g0 := Null.AddValueIn(e0, id("v1")).AddValueIn(e1, id("v0")) g0 := Null.AddValueIn(e0, v1).AddValueIn(e1, v0)
g1 := g0.DelValueIn(e0, id("v1")) g1 := g0.DelValueIn(e0, v1)
assert.True(t, Equal(Null.AddValueIn(e1, id("v0")), g1)) assert.True(t, Equal(Null.AddValueIn(e1, v0), g1))
assert.NotNil(t, g1.Value(id("v0"))) assert.NotNil(t, g1.ValueVertex(v0))
assert.NotNil(t, g1.Value(id("v1"))) assert.NotNil(t, g1.ValueVertex(v1))
} }
ej := NewValue("ej")
{ // removing to's only edge, sub-nodes have edge to each other { // removing to's only edge, sub-nodes have edge to each other
ej := JunctionOut([]OpenEdge{ oej := JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("ej0")), ValueOut(v0, ej0),
ValueOut(id("v1"), id("ej0")), ValueOut(v1, ej0),
}, id("ej")) }, ej)
g0 := Null.AddValueIn(ej, id("v2")) g0 := Null.AddValueIn(oej, v2)
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
g1 := g0.AddValueIn(e0, id("v1")) g1 := g0.AddValueIn(e0, v1)
g2 := g1.DelValueIn(ej, id("v2")) g2 := g1.DelValueIn(oej, v2)
assert.True(t, Equal(Null.AddValueIn(e0, id("v1")), g2)) assert.True(t, Equal(Null.AddValueIn(e0, v1), g2))
assert.NotNil(t, g2.Value(id("v0"))) assert.NotNil(t, g2.ValueVertex(v0))
assert.NotNil(t, g2.Value(id("v1"))) assert.NotNil(t, g2.ValueVertex(v1))
assert.Nil(t, g2.Value(id("v2"))) assert.Nil(t, g2.ValueVertex(v2))
} }
} }
@ -393,110 +415,124 @@ func TestGraphUnion(t *T) {
return ga return ga
} }
v0 := NewValue("v0")
v1 := NewValue("v1")
e0 := NewValue("e0")
{ // Union with Null { // Union with Null
assert.True(t, Equal(Null, Null.Union(Null))) assert.True(t, Equal(Null, Null.Union(Null)))
g := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g := Null.AddValueIn(ValueOut(v0, e0), v1)
assert.True(t, Equal(g, assertUnion(g, Null))) assert.True(t, Equal(g, assertUnion(g, Null)))
} }
v2 := NewValue("v2")
v3 := NewValue("v3")
e1 := NewValue("e1")
{ // Two disparate graphs union'd { // Two disparate graphs union'd
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g1 := Null.AddValueIn(ValueOut(id("v2"), id("e1")), id("v3")) g1 := Null.AddValueIn(ValueOut(v2, e1), v3)
g := assertUnion(g0, g1) g := assertUnion(g0, g1)
assertVertexEqual(t, value("v0"), g.Value(id("v0"))) assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value("v1", edge("e0", value("v0"))), g.Value(id("v1"))) assertVertexEqual(t, value(v1, edge(e0, value(v0))), g.ValueVertex(v1))
assertVertexEqual(t, value("v2"), g.Value(id("v2"))) assertVertexEqual(t, value(v2), g.ValueVertex(v2))
assertVertexEqual(t, value("v3", edge("e1", value("v2"))), g.Value(id("v3"))) assertVertexEqual(t, value(v3, edge(e1, value(v2))), g.ValueVertex(v3))
} }
va0, vb0 := NewValue("va0"), NewValue("vb0")
va1, vb1 := NewValue("va1"), NewValue("vb1")
va2, vb2 := NewValue("va2"), NewValue("vb2")
ea0, eb0 := NewValue("ea0"), NewValue("eb0")
ea1, eb1 := NewValue("ea1"), NewValue("eb1")
eaj, ebj := NewValue("eaj"), NewValue("ebj")
{ // Two disparate graphs with junctions { // Two disparate graphs with junctions
ga := Null.AddValueIn(JunctionOut([]OpenEdge{ ga := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("va0"), id("ea0")), ValueOut(va0, ea0),
ValueOut(id("va1"), id("ea1")), ValueOut(va1, ea1),
}, id("eaj")), id("va2")) }, eaj), va2)
gb := Null.AddValueIn(JunctionOut([]OpenEdge{ gb := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("vb0"), id("eb0")), ValueOut(vb0, eb0),
ValueOut(id("vb1"), id("eb1")), ValueOut(vb1, eb1),
}, id("ebj")), id("vb2")) }, ebj), vb2)
g := assertUnion(ga, gb) g := assertUnion(ga, gb)
assertVertexEqual(t, value("va0"), g.Value(id("va0"))) assertVertexEqual(t, value(va0), g.ValueVertex(va0))
assertVertexEqual(t, value("va1"), g.Value(id("va1"))) assertVertexEqual(t, value(va1), g.ValueVertex(va1))
assertVertexEqual(t, assertVertexEqual(t,
value("va2", junction("eaj", value(va2, junction(eaj,
edge("ea0", value("va0")), edge(ea0, value(va0)),
edge("ea1", value("va1")))), edge(ea1, value(va1)))),
g.Value(id("va2")), g.ValueVertex(va2),
) )
assertVertexEqual(t, value("vb0"), g.Value(id("vb0"))) assertVertexEqual(t, value(vb0), g.ValueVertex(vb0))
assertVertexEqual(t, value("vb1"), g.Value(id("vb1"))) assertVertexEqual(t, value(vb1), g.ValueVertex(vb1))
assertVertexEqual(t, assertVertexEqual(t,
value("vb2", junction("ebj", value(vb2, junction(ebj,
edge("eb0", value("vb0")), edge(eb0, value(vb0)),
edge("eb1", value("vb1")))), edge(eb1, value(vb1)))),
g.Value(id("vb2")), g.ValueVertex(vb2),
) )
} }
{ // Two partially overlapping graphs { // Two partially overlapping graphs
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2")) g0 := Null.AddValueIn(ValueOut(v0, e0), v2)
g1 := Null.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) g1 := Null.AddValueIn(ValueOut(v1, e1), v2)
g := assertUnion(g0, g1) g := assertUnion(g0, g1)
assertVertexEqual(t, value("v0"), g.Value(id("v0"))) assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value("v1"), g.Value(id("v1"))) assertVertexEqual(t, value(v1), g.ValueVertex(v1))
assertVertexEqual(t, assertVertexEqual(t,
value("v2", value(v2,
edge("e0", value("v0")), edge(e0, value(v0)),
edge("e1", value("v1")), edge(e1, value(v1)),
), ),
g.Value(id("v2")), g.ValueVertex(v2),
) )
} }
ej0 := NewValue("ej0")
ej1 := NewValue("ej1")
{ // two partially overlapping graphs with junctions { // two partially overlapping graphs with junctions
g0 := Null.AddValueIn(JunctionOut([]OpenEdge{ g0 := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")), ValueOut(v0, e0),
ValueOut(id("v1"), id("e1")), ValueOut(v1, e1),
}, id("ej0")), id("v2")) }, ej0), v2)
g1 := Null.AddValueIn(JunctionOut([]OpenEdge{ g1 := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")), ValueOut(v0, e0),
ValueOut(id("v1"), id("e1")), ValueOut(v1, e1),
}, id("ej1")), id("v2")) }, ej1), v2)
g := assertUnion(g0, g1) g := assertUnion(g0, g1)
assertVertexEqual(t, value("v0"), g.Value(id("v0"))) assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value("v1"), g.Value(id("v1"))) assertVertexEqual(t, value(v1), g.ValueVertex(v1))
assertVertexEqual(t, assertVertexEqual(t,
value("v2", value(v2,
junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))), junction(ej0, edge(e0, value(v0)), edge(e1, value(v1))),
junction("ej1", edge("e0", value("v0")), edge("e1", value("v1"))), junction(ej1, edge(e0, value(v0)), edge(e1, value(v1))),
), ),
g.Value(id("v2")), g.ValueVertex(v2),
) )
} }
{ // Two equal graphs { // Two equal graphs
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g := assertUnion(g0, g0) g := assertUnion(g0, g0)
assertVertexEqual(t, value("v0"), g.Value(id("v0"))) assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, assertVertexEqual(t,
value("v1", edge("e0", value("v0"))), value(v1, edge(e0, value(v0))),
g.Value(id("v1")), g.ValueVertex(v1),
) )
} }
{ // Two equal graphs with junctions { // Two equal graphs with junctions
g0 := Null.AddValueIn(JunctionOut([]OpenEdge{ g0 := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")), ValueOut(v0, e0),
ValueOut(id("v1"), id("e1")), ValueOut(v1, e1),
}, id("ej0")), id("v2")) }, ej0), v2)
g := assertUnion(g0, g0) g := assertUnion(g0, g0)
assertVertexEqual(t, value("v0"), g.Value(id("v0"))) assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value("v1"), g.Value(id("v1"))) assertVertexEqual(t, value(v1), g.ValueVertex(v1))
assertVertexEqual(t, assertVertexEqual(t,
value("v2", value(v2,
junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))), junction(ej0, edge(e0, value(v0)), edge(e1, value(v1))),
), ),
g.Value(id("v2")), g.ValueVertex(v2),
) )
} }
} }
@ -514,35 +550,42 @@ func TestGraphEqual(t *T) {
assertEqual(Null, Null) // duh assertEqual(Null, Null) // duh
v0 := NewValue("v0")
v1 := NewValue("v1")
v2 := NewValue("v2")
e0 := NewValue("e0")
e1 := NewValue("e1")
e1a, e1b := NewValue("e1a"), NewValue("e1b")
{ {
// graph is equal to itself, not to null // graph is equal to itself, not to null
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
g0 := Null.AddValueIn(e0, id("v1")) g0 := Null.AddValueIn(e0, v1)
assertNotEqual(g0, Null) assertNotEqual(g0, Null)
assertEqual(g0, g0) assertEqual(g0, g0)
// adding the an existing edge again shouldn't do anything // adding the an existing edge again shouldn't do anything
assertEqual(g0, g0.AddValueIn(e0, id("v1"))) assertEqual(g0, g0.AddValueIn(e0, v1))
// g1a and g1b have the same vertices, but the edges are different // g1a and g1b have the same vertices, but the edges are different
g1a := g0.AddValueIn(ValueOut(id("v0"), id("e1a")), id("v2")) g1a := g0.AddValueIn(ValueOut(v0, e1a), v2)
g1b := g0.AddValueIn(ValueOut(id("v0"), id("e1b")), id("v2")) g1b := g0.AddValueIn(ValueOut(v0, e1b), v2)
assertNotEqual(g1a, g1b) assertNotEqual(g1a, g1b)
} }
{ // equal construction should yield equality, even if out of order { // equal construction should yield equality, even if out of order
ga := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) ga := Null.AddValueIn(ValueOut(v0, e0), v1)
ga = ga.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) ga = ga.AddValueIn(ValueOut(v1, e1), v2)
gb := Null.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2")) gb := Null.AddValueIn(ValueOut(v1, e1), v2)
gb = gb.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1")) gb = gb.AddValueIn(ValueOut(v0, e0), v1)
assertEqual(ga, gb) assertEqual(ga, gb)
} }
ej := NewValue("ej")
{ // junction basic test { // junction basic test
e0 := ValueOut(id("v0"), id("e0")) e0 := ValueOut(v0, e0)
e1 := ValueOut(id("v1"), id("e1")) e1 := ValueOut(v1, e1)
ga := Null.AddValueIn(JunctionOut([]OpenEdge{e0, e1}, id("ej")), id("v2")) ga := Null.AddValueIn(JunctionOut([]OpenEdge{e0, e1}, ej), v2)
gb := Null.AddValueIn(JunctionOut([]OpenEdge{e1, e0}, id("ej")), id("v2")) gb := Null.AddValueIn(JunctionOut([]OpenEdge{e1, e0}, ej), v2)
assertEqual(ga, ga) assertEqual(ga, ga)
assertNotEqual(ga, gb) assertNotEqual(ga, gb)
} }

View File

@ -42,8 +42,8 @@ func boxFromVertex(v *gg.Vertex, flowDir geo.XY) box {
numIn: len(v.In), numIn: len(v.In),
numOut: len(v.Out), numOut: len(v.Out),
} }
if v.VertexType == gg.Value { if v.VertexType == gg.ValueVertex {
b.body = string(v.Value.(gg.Str)) b.body = v.Value.V.(string)
} }
return b return b
} }

View File

@ -17,28 +17,38 @@ type Constraint struct {
LT string LT string
} }
const ltEdge = gg.Str("lt") var ltEdge = gg.NewValue("lt")
// Engine processes sets of constraints to generate an output // Engine processes sets of constraints to generate an output
type Engine struct { type Engine struct {
g *gg.Graph g *gg.Graph
vals map[string]gg.Value
} }
// NewEngine initializes and returns an empty Engine // NewEngine initializes and returns an empty Engine
func NewEngine() *Engine { func NewEngine() *Engine {
return &Engine{g: gg.Null} return &Engine{g: gg.Null, vals: map[string]gg.Value{}}
}
func (e *Engine) getVal(elem string) gg.Value {
if val, ok := e.vals[elem]; ok {
return val
}
val := gg.NewValue(elem)
e.vals[elem] = val
return val
} }
// AddConstraint adds the given constraint to the engine's set, returns false if // AddConstraint adds the given constraint to the engine's set, returns false if
// the constraint couldn't be added due to a conflict with a previous constraint // the constraint couldn't be added due to a conflict with a previous constraint
func (e *Engine) AddConstraint(c Constraint) bool { func (e *Engine) AddConstraint(c Constraint) bool {
elem := gg.Str(c.Elem) elem := e.getVal(c.Elem)
g := e.g.AddValueIn(gg.ValueOut(elem, ltEdge), gg.Str(c.LT)) g := e.g.AddValueIn(gg.ValueOut(elem, ltEdge), e.getVal(c.LT))
// Check for loops in g starting at c.Elem, bail if there are any // Check for loops in g starting at c.Elem, bail if there are any
{ {
seen := map[*gg.Vertex]bool{} seen := map[*gg.Vertex]bool{}
start := g.Value(elem) start := g.ValueVertex(elem)
var hasLoop func(v *gg.Vertex) bool var hasLoop func(v *gg.Vertex) bool
hasLoop = func(v *gg.Vertex) bool { hasLoop = func(v *gg.Vertex) bool {
if seen[v] { if seen[v] {
@ -66,12 +76,12 @@ func (e *Engine) AddConstraint(c Constraint) bool {
// engine and whose value is known to be zero. // engine and whose value is known to be zero.
func (e *Engine) Solve() map[string]int { func (e *Engine) Solve() map[string]int {
m := map[string]int{} m := map[string]int{}
if len(e.g.Values()) == 0 { if len(e.g.ValueVertices()) == 0 {
return m return m
} }
vElem := func(v *gg.Vertex) string { vElem := func(v *gg.Vertex) string {
return string(v.Value.(gg.Str)) return v.Value.V.(string)
} }
// 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

View File

@ -35,25 +35,36 @@ func debugf(str string, args ...interface{}) {
fmt.Fprintf(os.Stderr, str, args...) fmt.Fprintf(os.Stderr, str, args...)
} }
func mkGraph() *gg.Graph { func mkGraph() (*gg.Graph, gg.Value) {
aE0 := gg.ValueOut(gg.Str("a"), gg.Str("aE0")) a := gg.NewValue("a")
aE1 := gg.ValueOut(gg.Str("a"), gg.Str("aE1")) aE0 := gg.NewValue("aE0")
aE2 := gg.ValueOut(gg.Str("a"), gg.Str("aE2")) aE1 := gg.NewValue("aE1")
aE3 := gg.ValueOut(gg.Str("a"), gg.Str("aE3")) aE2 := gg.NewValue("aE2")
aE3 := gg.NewValue("aE3")
b0 := gg.NewValue("b0")
b1 := gg.NewValue("b1")
b2 := gg.NewValue("b2")
b3 := gg.NewValue("b3")
oaE0 := gg.ValueOut(a, aE0)
oaE1 := gg.ValueOut(a, aE1)
oaE2 := gg.ValueOut(a, aE2)
oaE3 := gg.ValueOut(a, aE3)
g := gg.Null g := gg.Null
g = g.AddValueIn(aE0, gg.Str("b0")) g = g.AddValueIn(oaE0, b0)
g = g.AddValueIn(aE1, gg.Str("b1")) g = g.AddValueIn(oaE1, b1)
g = g.AddValueIn(aE2, gg.Str("b2")) g = g.AddValueIn(oaE2, b2)
g = g.AddValueIn(aE3, gg.Str("b3")) g = g.AddValueIn(oaE3, b3)
c := gg.NewValue("c")
empty := gg.NewValue("")
jE := gg.JunctionOut([]gg.OpenEdge{ jE := gg.JunctionOut([]gg.OpenEdge{
gg.ValueOut(gg.Str("b0"), gg.Str("")), gg.ValueOut(b0, empty),
gg.ValueOut(gg.Str("b1"), gg.Str("")), gg.ValueOut(b1, empty),
gg.ValueOut(gg.Str("b2"), gg.Str("")), gg.ValueOut(b2, empty),
gg.ValueOut(gg.Str("b3"), gg.Str("")), gg.ValueOut(b3, empty),
}, gg.Str("jE")) }, gg.NewValue("jE"))
g = g.AddValueIn(jE, gg.Str("c")) g = g.AddValueIn(jE, c)
return g return g, c
} }
//func mkGraph() *gg.Graph { //func mkGraph() *gg.Graph {
@ -68,11 +79,12 @@ func main() {
term.Reset() term.Reset()
term.HideCursor() term.HideCursor()
g, start := mkGraph()
v := view{ v := view{
g: mkGraph(), g: g,
primFlowDir: geo.Right, primFlowDir: geo.Right,
secFlowDir: geo.Down, secFlowDir: geo.Down,
start: gg.Str("c"), start: start,
center: geo.Zero.Midpoint(term.WindowSize(), rounder), center: geo.Zero.Midpoint(term.WindowSize(), rounder),
} }

View File

@ -24,12 +24,12 @@ func posSolve(g *gg.Graph) [][]*gg.Vertex {
secEng := constraint.NewEngine() secEng := constraint.NewEngine()
strM := g.ByID() strM := g.ByID()
for vID, v := range strM { for _, v := range strM {
var prevIn *gg.Vertex var prevIn *gg.Vertex
for _, e := range v.In { for _, e := range v.In {
primEng.AddConstraint(constraint.Constraint{ primEng.AddConstraint(constraint.Constraint{
Elem: e.From.ID, Elem: e.From.ID,
LT: vID, LT: v.ID,
}) })
if prevIn != nil { if prevIn != nil {
secEng.AddConstraint(constraint.Constraint{ secEng.AddConstraint(constraint.Constraint{
@ -103,7 +103,7 @@ func centerBoxes(boxes []*box, around geo.XY) {
type view struct { type view struct {
g *gg.Graph g *gg.Graph
primFlowDir, secFlowDir geo.XY primFlowDir, secFlowDir geo.XY
start gg.Str start gg.Value
center geo.XY center geo.XY
} }