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

183
gg/gg.go
View File

@ -2,63 +2,62 @@
package gg
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"hash"
"strings"
)
// TODO instead of Identifier being public, make it encoding.TextMarshaler
// Identifier is implemented by any value which can return a unique string for
// itself via an Identify method
type Identifier interface {
Identify(hash.Hash)
// 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.
type Value struct {
ID string
V interface{}
}
func identify(i Identifier) string {
h := md5.New()
i.Identify(h)
return hex.EncodeToString(h.Sum(nil))
// NewValue returns a Value instance wrapping any go value. The Value returned
// will be independent of the passed in go value. So if the same go value is
// passed in twice then the two returned Value instances will be treated as
// 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
type VertexType string
const (
// Value is a Vertex which contains exactly one value and has at least one
// edge (either input or output)
Value VertexType = "value"
// ValueVertex is a Vertex which contains exactly one value and has at least
// one edge (either input or output)
ValueVertex VertexType = "value"
// Junction is a Vertex which contains two or more in edges and exactly one
// out edge
Junction VertexType = "junction"
// JunctionVertex is a Vertex which contains two or more in edges and
// exactly one out edge
JunctionVertex VertexType = "junction"
)
// Edge is a uni-directional connection between two vertices with an attribute
// value
type Edge struct {
From *Vertex
Value Identifier
Value Value
To *Vertex
}
// Vertex is a vertex in a Graph. No fields should be modified directly, only
// through method calls
type Vertex struct {
ID string
VertexType
ID string // identifier of the vertex, unique within the graph
Value Identifier // Value is valid if-and-only-if VertexType is Value
Value Value // Value is valid if-and-only-if VertexType is ValueVertex
In, Out []Edge
}
@ -72,14 +71,11 @@ type OpenEdge struct {
// already that will need to be taken into account when persisting into the
// graph
fromV vertex
val Identifier
val Value
}
// Identify implements the Identifier interface
func (oe OpenEdge) Identify(h hash.Hash) {
fmt.Fprintln(h, "openEdge")
oe.fromV.Identify(h)
oe.val.Identify(h)
func (oe OpenEdge) id() string {
return fmt.Sprintf("(%s,%s)", oe.fromV.id, oe.val.ID)
}
// 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
// and the top-level one's in value is used to properly connect it.
type vertex struct {
id string
VertexType
val Identifier
val Value
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 {
cp := v
cp.in = make([]OpenEdge, len(v.in))
@ -125,9 +105,9 @@ func (v vertex) cp() vertex {
}
func (v vertex) hasOpenEdge(oe OpenEdge) bool {
oeID := identify(oe)
oeID := oe.id()
for _, in := range v.in {
if identify(in) == oeID {
if in.id() == oeID {
return true
}
}
@ -135,9 +115,9 @@ func (v vertex) hasOpenEdge(oe OpenEdge) bool {
}
func (v vertex) cpAndDelOpenEdge(oe OpenEdge) (vertex, bool) {
oeID := identify(oe)
oeID := oe.id()
for i, in := range v.in {
if identify(in) == oeID {
if in.id() == oeID {
v = v.cp()
v.in = append(v.in[:i], v.in[i+1:]...)
return v, true
@ -168,8 +148,8 @@ func (g *Graph) cp() *Graph {
cp := &Graph{
vM: make(map[string]vertex, len(g.vM)),
}
for id, v := range g.vM {
cp.vM[id] = v
for vID, v := range g.vM {
cp.vM[vID] = v
}
return cp
}
@ -178,16 +158,17 @@ func (g *Graph) cp() *Graph {
// Graph creation
// 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.
//
// 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
// same Vertex instance in the constructed Graph.
func ValueOut(val, edgeVal Identifier) OpenEdge {
func ValueOut(val, edgeVal Value) OpenEdge {
return OpenEdge{
fromV: vertex{
VertexType: Value,
id: val.ID,
VertexType: ValueVertex,
val: val,
},
val: edgeVal,
@ -195,16 +176,21 @@ func ValueOut(val, edgeVal Identifier) OpenEdge {
}
// JunctionOut creates a OpenEdge which, when used to construct a Graph,
// represents an edge (with edgeVal attached to it) leaving the Junction Vertex
// comprised of the given ordered-set of input edges.
// represents an edge (with edgeVal attached to it) coming from the
// JunctionVertex comprised of the given ordered-set of input edges.
//
// 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 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{
fromV: vertex{
VertexType: Junction,
id: "[" + strings.Join(inIDs, ",") + "]",
VertexType: JunctionVertex,
in: in,
},
val: edgeVal,
@ -215,12 +201,13 @@ func JunctionOut(in []OpenEdge, edgeVal Identifier) OpenEdge {
// 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
// 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{
VertexType: Value,
id: val.ID,
VertexType: ValueVertex,
val: val,
}
toID := identify(to)
toID := to.id
// if to is already in the graph, pull it out, as it might have existing in
// 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
var persist func(vertex)
persist = func(v vertex) {
vID := identify(v)
if v.VertexType == Value {
if v.VertexType == ValueVertex {
vID := v.id
if _, ok := g.vM[vID]; !ok {
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
// 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 Identifier) *Graph {
func (g *Graph) DelValueIn(oe OpenEdge, val Value) *Graph {
to := vertex{
VertexType: Value,
id: val.ID,
VertexType: ValueVertex,
val: val,
}
toID := identify(to)
toID := to.id
// pull to out of the graph. if it's not there then bail
var ok bool
@ -293,9 +281,9 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph {
var connectedTo func(string, vertex) bool
connectedTo = func(vID string, curr vertex) bool {
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
} else if in.fromV.VertexType == Junction && connectedTo(vID, in.fromV) {
} else if in.fromV.VertexType == JunctionVertex && connectedTo(vID, in.fromV) {
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
// nodes in the graph
isOrphaned := func(v vertex) bool {
vID := identify(v)
vID := v.id
if v, ok := g.vM[vID]; ok && len(v.in) > 0 {
return false
}
@ -328,11 +316,11 @@ func (g *Graph) DelValueIn(oe OpenEdge, val Identifier) *Graph {
// Vertices referenced in it which are now orphaned
var rmOrphaned func(OpenEdge)
rmOrphaned = func(oe OpenEdge) {
if oe.fromV.VertexType == Value && isOrphaned(oe.fromV) {
delete(g.vM, identify(oe.fromV))
} else if oe.fromV.VertexType == Junction {
for _, juncHe := range oe.fromV.in {
rmOrphaned(juncHe)
if oe.fromV.VertexType == ValueVertex && isOrphaned(oe.fromV) {
delete(g.vM, oe.fromV.id)
} else if oe.fromV.VertexType == JunctionVertex {
for _, juncOe := range oe.fromV.in {
rmOrphaned(juncOe)
}
}
}
@ -377,18 +365,17 @@ func (g *Graph) makeView() {
var getV func(vertex, bool) *Vertex
getV = func(v vertex, top bool) *Vertex {
vID := identify(v)
V, ok := g.all[vID]
V, ok := g.all[v.id]
if !ok {
V = &Vertex{VertexType: v.VertexType, ID: vID, Value: v.val}
g.all[vID] = V
V = &Vertex{ID: v.id, VertexType: v.VertexType, Value: v.val}
g.all[v.id] = V
}
// 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:
// * top is true
// * this is a junction's first time being gotten
if !top && (ok || v.VertexType != Junction) {
if !top && (ok || v.VertexType != JunctionVertex) {
return V
}
@ -400,8 +387,8 @@ func (g *Graph) makeView() {
V.In = append(V.In, e)
}
if v.VertexType == Value {
g.byVal[identify(v.val)] = V
if v.VertexType == ValueVertex {
g.byVal[v.val.ID] = 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
// contain a vertex for the value then nil is returned
func (g *Graph) Value(val Identifier) *Vertex {
// ValueVertex returns the Value Vertex for the given value. If the Graph
// doesn't contain a vertex for the value then nil is returned
func (g *Graph) ValueVertex(val Value) *Vertex {
g.makeView()
return g.byVal[identify(val)]
return g.byVal[val.ID]
}
// Values returns all Value Vertices in the Graph
func (g *Graph) Values() []*Vertex {
// ValueVertices returns all Value Vertices in the Graph
func (g *Graph) ValueVertices() []*Vertex {
g.makeView()
vv := make([]*Vertex, 0, len(g.byVal))
for _, v := range g.byVal {

View File

@ -1,44 +1,30 @@
package gg
import (
"fmt"
"hash"
. "testing"
"github.com/stretchr/testify/assert"
)
type idAny struct {
i interface{}
func edge(val Value, from *Vertex) Edge {
return Edge{Value: val, From: from}
}
func (i idAny) Identify(h hash.Hash) {
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 {
func value(val Value, in ...Edge) *Vertex {
return &Vertex{
VertexType: Value,
Value: id(val),
VertexType: ValueVertex,
Value: val,
In: in,
}
}
func junction(val string, in ...Edge) Edge {
func junction(val Value, in ...Edge) Edge {
return Edge{
From: &Vertex{
VertexType: Junction,
VertexType: JunctionVertex,
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 {
assert.NotContains(t, seen, v, msgAndArgs...)
seen[v] = true
if v.VertexType == Value {
if v.VertexType == ValueVertex {
gotVals++
} else {
gotJuncs++
@ -102,122 +88,140 @@ func mkTest(name string, out func() *Graph, numVals, numJuncs int, exp ...*Verte
}
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{
mkTest(
"values-basic",
func() *Graph {
return Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
return Null.AddValueIn(ValueOut(v0, e0), v1)
},
2, 0,
value("v0"),
value("v1", edge("e0", value("v0"))),
value(v0),
value(v1, edge(e0, value(v0))),
),
mkTest(
"values-2edges",
func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2"))
return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v2)
return g0.AddValueIn(ValueOut(v1, e1), v2)
},
3, 0,
value("v0"),
value("v1"),
value("v2",
edge("e0", value("v0")),
edge("e1", value("v1")),
value(v0),
value(v1),
value(v2,
edge(e0, value(v0)),
edge(e1, value(v1)),
),
),
mkTest(
"values-separate",
func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
return g0.AddValueIn(ValueOut(id("v2"), id("e2")), id("v3"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
return g0.AddValueIn(ValueOut(v2, e2), v3)
},
4, 0,
value("v0"),
value("v1", edge("e0", value("v0"))),
value("v2"),
value("v3", edge("e2", value("v2"))),
value(v0),
value(v1, edge(e0, value(v0))),
value(v2),
value(v3, edge(e2, value(v2))),
),
mkTest(
"values-circular",
func() *Graph {
return Null.AddValueIn(ValueOut(id("v0"), id("e")), id("v0"))
return Null.AddValueIn(ValueOut(v0, e0), v0)
},
1, 0,
value("v0", edge("e", value("v0"))),
value(v0, edge(e0, value(v0))),
),
mkTest(
"values-circular2",
func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
return g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v0"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
return g0.AddValueIn(ValueOut(v1, e1), v0)
},
2, 0,
value("v0", edge("e1", value("v1", edge("e0", value("v0"))))),
value("v1", edge("e0", value("v0", edge("e1", value("v1"))))),
value(v0, edge(e1, value(v1, edge(e0, value(v0))))),
value(v1, edge(e0, value(v0, edge(e1, value(v1))))),
),
mkTest(
"values-circular3",
func() *Graph {
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
g1 := g0.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2"))
return g1.AddValueIn(ValueOut(id("v2"), id("e2")), id("v1"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g1 := g0.AddValueIn(ValueOut(v1, e1), v2)
return g1.AddValueIn(ValueOut(v2, e2), v1)
},
3, 0,
value("v0"),
value("v1",
edge("e0", value("v0")),
edge("e2", value("v2", edge("e1", value("v1")))),
value(v0),
value(v1,
edge(e0, value(v0)),
edge(e2, value(v2, edge(e1, value(v1)))),
),
value("v2", edge("e1", value("v1",
edge("e0", value("v0")),
edge("e2", value("v2")),
value(v2, edge(e1, value(v1,
edge(e0, value(v0)),
edge(e2, value(v2)),
))),
),
mkTest(
"junction-basic",
func() *Graph {
e0 := ValueOut(id("v0"), id("e0"))
e1 := ValueOut(id("v1"), id("e1"))
ej0 := JunctionOut([]OpenEdge{e0, e1}, id("ej0"))
return Null.AddValueIn(ej0, id("v2"))
e0 := ValueOut(v0, e0)
e1 := ValueOut(v1, e1)
ej0 := JunctionOut([]OpenEdge{e0, e1}, ej0)
return Null.AddValueIn(ej0, v2)
},
3, 1,
value("v0"), value("v1"),
value("v2", junction("ej0",
edge("e0", value("v0")),
edge("e1", value("v1")),
value(v0), value(v1),
value(v2, junction(ej0,
edge(e0, value(v0)),
edge(e1, value(v1)),
)),
),
mkTest(
"junction-basic2",
func() *Graph {
e00 := ValueOut(id("v0"), id("e00"))
e10 := ValueOut(id("v1"), id("e10"))
ej0 := JunctionOut([]OpenEdge{e00, e10}, id("ej0"))
e01 := ValueOut(id("v0"), id("e01"))
e11 := ValueOut(id("v1"), id("e11"))
ej1 := JunctionOut([]OpenEdge{e01, e11}, id("ej1"))
ej2 := JunctionOut([]OpenEdge{ej0, ej1}, id("ej2"))
return Null.AddValueIn(ej2, id("v2"))
e00 := ValueOut(v0, e00)
e10 := ValueOut(v1, e10)
ej0 := JunctionOut([]OpenEdge{e00, e10}, ej0)
e01 := ValueOut(v0, e01)
e11 := ValueOut(v1, e11)
ej1 := JunctionOut([]OpenEdge{e01, e11}, ej1)
ej2 := JunctionOut([]OpenEdge{ej0, ej1}, ej2)
return Null.AddValueIn(ej2, v2)
},
3, 3,
value("v0"), value("v1"),
value("v2", junction("ej2",
junction("ej0",
edge("e00", value("v0")),
edge("e10", value("v1")),
value(v0), value(v1),
value(v2, junction(ej2,
junction(ej0,
edge(e00, value(v0)),
edge(e10, value(v1)),
),
junction("ej1",
edge("e01", value("v0")),
edge("e11", value("v1")),
junction(ej1,
edge(e01, value(v0)),
edge(e11, value(v1)),
),
)),
),
@ -225,27 +229,27 @@ func TestGraph(t *T) {
mkTest(
"junction-circular",
func() *Graph {
e0 := ValueOut(id("v0"), id("e0"))
e1 := ValueOut(id("v1"), id("e1"))
ej0 := JunctionOut([]OpenEdge{e0, e1}, id("ej0"))
g0 := Null.AddValueIn(ej0, id("v2"))
e20 := ValueOut(id("v2"), id("e20"))
g1 := g0.AddValueIn(e20, id("v0"))
e21 := ValueOut(id("v2"), id("e21"))
return g1.AddValueIn(e21, id("v1"))
e0 := ValueOut(v0, e0)
e1 := ValueOut(v1, e1)
ej0 := JunctionOut([]OpenEdge{e0, e1}, ej0)
g0 := Null.AddValueIn(ej0, v2)
e20 := ValueOut(v2, e20)
g1 := g0.AddValueIn(e20, v0)
e21 := ValueOut(v2, e21)
return g1.AddValueIn(e21, v1)
},
3, 1,
value("v0", edge("e20", value("v2", junction("ej0",
edge("e0", value("v0")),
edge("e1", value("v1", edge("e21", value("v2")))),
value(v0, edge(e20, value(v2, junction(ej0,
edge(e0, value(v0)),
edge(e1, value(v1, edge(e21, value(v2)))),
)))),
value("v1", edge("e21", value("v2", junction("ej0",
edge("e0", value("v0", edge("e20", value("v2")))),
edge("e1", value("v1")),
value(v1, edge(e21, value(v2, junction(ej0,
edge(e0, value(v0, edge(e20, value(v2)))),
edge(e1, value(v1)),
)))),
value("v2", junction("ej0",
edge("e0", value("v0", edge("e20", value("v2")))),
edge("e1", value("v1", edge("e21", value("v2")))),
value(v2, junction(ej0,
edge(e0, value(v0, edge(e20, 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 {
msgAndArgs := []interface{}{
"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...) {
continue
}
@ -279,109 +283,127 @@ func TestGraph(t *T) {
}
func TestGraphImmutability(t *T) {
e0 := ValueOut(id("v0"), id("e0"))
g0 := Null.AddValueIn(e0, id("v1"))
assert.Nil(t, Null.Value(id("v0")))
assert.Nil(t, Null.Value(id("v1")))
assert.NotNil(t, g0.Value(id("v0")))
assert.NotNil(t, g0.Value(id("v1")))
v0 := NewValue("v0")
v1 := NewValue("v1")
e0 := NewValue("e0")
oe0 := ValueOut(v0, e0)
g0 := Null.AddValueIn(oe0, 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
e1 := ValueOut(id("v2"), id("e1"))
g1a := g0.AddValueIn(e1, id("v3a"))
g1b := g0.AddValueIn(e1, id("v3b"))
assertVertexEqual(t, value("v3a", edge("e1", value("v2"))), g1a.Value(id("v3a")))
assert.Nil(t, g1a.Value(id("v3b")))
assertVertexEqual(t, value("v3b", edge("e1", value("v2"))), g1b.Value(id("v3b")))
assert.Nil(t, g1b.Value(id("v3a")))
v2 := NewValue("v2")
v3a, v3b := NewValue("v3a"), NewValue("v3b")
e1 := NewValue("e1")
oe1 := ValueOut(v2, e1)
g1a := g0.AddValueIn(oe1, v3a)
g1b := g0.AddValueIn(oe1, v3b)
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
g2 := g0.AddValueIn(e1, id("v3")).AddValueIn(e1, id("v4"))
assert.Nil(t, g2.Value(id("v3b")))
assert.Nil(t, g2.Value(id("v3a")))
assertVertexEqual(t, value("v3", edge("e1", value("v2"))), g2.Value(id("v3")))
assertVertexEqual(t, value("v4", edge("e1", value("v2"))), g2.Value(id("v4")))
v3 := NewValue("v3")
v4 := NewValue("v4")
g2 := g0.AddValueIn(oe1, v3).AddValueIn(oe1, v4)
assert.Nil(t, g2.ValueVertex(v3b))
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) {
v0 := NewValue("v0")
v1 := NewValue("v1")
e0 := NewValue("e0")
{ // 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))
}
e1 := NewValue("e1")
{ // removing edge from vertex which doesn't have that edge
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
g1 := g0.DelValueIn(ValueOut(id("v0"), id("e1")), id("v1"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g1 := g0.DelValueIn(ValueOut(v0, e1), v1)
assert.True(t, Equal(g0, g1))
}
{ // removing only edge
oe := ValueOut(id("v0"), id("e0"))
g0 := Null.AddValueIn(oe, id("v1"))
g1 := g0.DelValueIn(oe, id("v1"))
oe := ValueOut(v0, e0)
g0 := Null.AddValueIn(oe, v1)
g1 := g0.DelValueIn(oe, v1)
assert.True(t, Equal(Null, g1))
}
ej0 := NewValue("ej0")
v2 := NewValue("v2")
{ // removing only edge (junction)
oe := JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")),
ValueOut(id("v1"), id("e1")),
}, id("ej0"))
g0 := Null.AddValueIn(oe, id("v2"))
g1 := g0.DelValueIn(oe, id("v2"))
ValueOut(v0, e0),
ValueOut(v1, e1),
}, ej0)
g0 := Null.AddValueIn(oe, v2)
g1 := g0.DelValueIn(oe, v2)
assert.True(t, Equal(Null, g1))
}
{ // removing one of two edges
oe := ValueOut(id("v1"), id("e0"))
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2"))
g1 := g0.AddValueIn(oe, id("v2"))
g2 := g1.DelValueIn(oe, id("v2"))
oe := ValueOut(v1, e0)
g0 := Null.AddValueIn(ValueOut(v0, e0), v2)
g1 := g0.AddValueIn(oe, v2)
g2 := g1.DelValueIn(oe, v2)
assert.True(t, Equal(g0, g2))
assert.NotNil(t, g2.Value(id("v0")))
assert.Nil(t, g2.Value(id("v1")))
assert.NotNil(t, g2.Value(id("v2")))
assert.NotNil(t, g2.ValueVertex(v0))
assert.Nil(t, g2.ValueVertex(v1))
assert.NotNil(t, g2.ValueVertex(v2))
}
e2 := NewValue("e2")
eja, ejb := NewValue("eja"), NewValue("ejb")
v3 := NewValue("v3")
{ // removing one of two edges (junction)
e0 := ValueOut(id("v0"), id("e0"))
e1 := ValueOut(id("v1"), id("e1"))
e2 := ValueOut(id("v2"), id("e2"))
oeA := JunctionOut([]OpenEdge{e0, e1}, id("oeA"))
oeB := JunctionOut([]OpenEdge{e1, e2}, id("oeB"))
g0a := Null.AddValueIn(oeA, id("v3"))
g0b := Null.AddValueIn(oeB, id("v3"))
g1 := g0a.Union(g0b).DelValueIn(oeA, id("v3"))
e0 := ValueOut(v0, e0)
e1 := ValueOut(v1, e1)
e2 := ValueOut(v2, e2)
oeA := JunctionOut([]OpenEdge{e0, e1}, eja)
oeB := JunctionOut([]OpenEdge{e1, e2}, ejb)
g0a := Null.AddValueIn(oeA, v3)
g0b := Null.AddValueIn(oeB, v3)
g1 := g0a.Union(g0b).DelValueIn(oeA, v3)
assert.True(t, Equal(g1, g0b))
assert.Nil(t, g1.Value(id("v0")))
assert.NotNil(t, g1.Value(id("v1")))
assert.NotNil(t, g1.Value(id("v2")))
assert.NotNil(t, g1.Value(id("v3")))
assert.Nil(t, g1.ValueVertex(v0))
assert.NotNil(t, g1.ValueVertex(v1))
assert.NotNil(t, g1.ValueVertex(v2))
assert.NotNil(t, g1.ValueVertex(v3))
}
{ // removing one of two edges in circular graph
e0 := ValueOut(id("v0"), id("e0"))
e1 := ValueOut(id("v1"), id("e1"))
g0 := Null.AddValueIn(e0, id("v1")).AddValueIn(e1, id("v0"))
g1 := g0.DelValueIn(e0, id("v1"))
assert.True(t, Equal(Null.AddValueIn(e1, id("v0")), g1))
assert.NotNil(t, g1.Value(id("v0")))
assert.NotNil(t, g1.Value(id("v1")))
e0 := ValueOut(v0, e0)
e1 := ValueOut(v1, e1)
g0 := Null.AddValueIn(e0, v1).AddValueIn(e1, v0)
g1 := g0.DelValueIn(e0, v1)
assert.True(t, Equal(Null.AddValueIn(e1, v0), g1))
assert.NotNil(t, g1.ValueVertex(v0))
assert.NotNil(t, g1.ValueVertex(v1))
}
ej := NewValue("ej")
{ // removing to's only edge, sub-nodes have edge to each other
ej := JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("ej0")),
ValueOut(id("v1"), id("ej0")),
}, id("ej"))
g0 := Null.AddValueIn(ej, id("v2"))
e0 := ValueOut(id("v0"), id("e0"))
g1 := g0.AddValueIn(e0, id("v1"))
g2 := g1.DelValueIn(ej, id("v2"))
assert.True(t, Equal(Null.AddValueIn(e0, id("v1")), g2))
assert.NotNil(t, g2.Value(id("v0")))
assert.NotNil(t, g2.Value(id("v1")))
assert.Nil(t, g2.Value(id("v2")))
oej := JunctionOut([]OpenEdge{
ValueOut(v0, ej0),
ValueOut(v1, ej0),
}, ej)
g0 := Null.AddValueIn(oej, v2)
e0 := ValueOut(v0, e0)
g1 := g0.AddValueIn(e0, v1)
g2 := g1.DelValueIn(oej, v2)
assert.True(t, Equal(Null.AddValueIn(e0, v1), g2))
assert.NotNil(t, g2.ValueVertex(v0))
assert.NotNil(t, g2.ValueVertex(v1))
assert.Nil(t, g2.ValueVertex(v2))
}
}
@ -393,110 +415,124 @@ func TestGraphUnion(t *T) {
return ga
}
v0 := NewValue("v0")
v1 := NewValue("v1")
e0 := NewValue("e0")
{ // Union with 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)))
}
v2 := NewValue("v2")
v3 := NewValue("v3")
e1 := NewValue("e1")
{ // Two disparate graphs union'd
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
g1 := Null.AddValueIn(ValueOut(id("v2"), id("e1")), id("v3"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g1 := Null.AddValueIn(ValueOut(v2, e1), v3)
g := assertUnion(g0, g1)
assertVertexEqual(t, value("v0"), g.Value(id("v0")))
assertVertexEqual(t, value("v1", edge("e0", value("v0"))), g.Value(id("v1")))
assertVertexEqual(t, value("v2"), g.Value(id("v2")))
assertVertexEqual(t, value("v3", edge("e1", value("v2"))), g.Value(id("v3")))
assertVertexEqual(t, value(v0), g.ValueVertex(v0))
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))
}
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
ga := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("va0"), id("ea0")),
ValueOut(id("va1"), id("ea1")),
}, id("eaj")), id("va2"))
ValueOut(va0, ea0),
ValueOut(va1, ea1),
}, eaj), va2)
gb := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("vb0"), id("eb0")),
ValueOut(id("vb1"), id("eb1")),
}, id("ebj")), id("vb2"))
ValueOut(vb0, eb0),
ValueOut(vb1, eb1),
}, ebj), vb2)
g := assertUnion(ga, gb)
assertVertexEqual(t, value("va0"), g.Value(id("va0")))
assertVertexEqual(t, value("va1"), g.Value(id("va1")))
assertVertexEqual(t, value(va0), g.ValueVertex(va0))
assertVertexEqual(t, value(va1), g.ValueVertex(va1))
assertVertexEqual(t,
value("va2", junction("eaj",
edge("ea0", value("va0")),
edge("ea1", value("va1")))),
g.Value(id("va2")),
value(va2, junction(eaj,
edge(ea0, value(va0)),
edge(ea1, value(va1)))),
g.ValueVertex(va2),
)
assertVertexEqual(t, value("vb0"), g.Value(id("vb0")))
assertVertexEqual(t, value("vb1"), g.Value(id("vb1")))
assertVertexEqual(t, value(vb0), g.ValueVertex(vb0))
assertVertexEqual(t, value(vb1), g.ValueVertex(vb1))
assertVertexEqual(t,
value("vb2", junction("ebj",
edge("eb0", value("vb0")),
edge("eb1", value("vb1")))),
g.Value(id("vb2")),
value(vb2, junction(ebj,
edge(eb0, value(vb0)),
edge(eb1, value(vb1)))),
g.ValueVertex(vb2),
)
}
{ // Two partially overlapping graphs
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v2"))
g1 := Null.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v2)
g1 := Null.AddValueIn(ValueOut(v1, e1), v2)
g := assertUnion(g0, g1)
assertVertexEqual(t, value("v0"), g.Value(id("v0")))
assertVertexEqual(t, value("v1"), g.Value(id("v1")))
assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value(v1), g.ValueVertex(v1))
assertVertexEqual(t,
value("v2",
edge("e0", value("v0")),
edge("e1", value("v1")),
value(v2,
edge(e0, value(v0)),
edge(e1, value(v1)),
),
g.Value(id("v2")),
g.ValueVertex(v2),
)
}
ej0 := NewValue("ej0")
ej1 := NewValue("ej1")
{ // two partially overlapping graphs with junctions
g0 := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")),
ValueOut(id("v1"), id("e1")),
}, id("ej0")), id("v2"))
ValueOut(v0, e0),
ValueOut(v1, e1),
}, ej0), v2)
g1 := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")),
ValueOut(id("v1"), id("e1")),
}, id("ej1")), id("v2"))
ValueOut(v0, e0),
ValueOut(v1, e1),
}, ej1), v2)
g := assertUnion(g0, g1)
assertVertexEqual(t, value("v0"), g.Value(id("v0")))
assertVertexEqual(t, value("v1"), g.Value(id("v1")))
assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value(v1), g.ValueVertex(v1))
assertVertexEqual(t,
value("v2",
junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))),
junction("ej1", edge("e0", value("v0")), edge("e1", value("v1"))),
value(v2,
junction(ej0, 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
g0 := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
g0 := Null.AddValueIn(ValueOut(v0, e0), v1)
g := assertUnion(g0, g0)
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")),
value(v1, edge(e0, value(v0))),
g.ValueVertex(v1),
)
}
{ // Two equal graphs with junctions
g0 := Null.AddValueIn(JunctionOut([]OpenEdge{
ValueOut(id("v0"), id("e0")),
ValueOut(id("v1"), id("e1")),
}, id("ej0")), id("v2"))
ValueOut(v0, e0),
ValueOut(v1, e1),
}, ej0), v2)
g := assertUnion(g0, g0)
assertVertexEqual(t, value("v0"), g.Value(id("v0")))
assertVertexEqual(t, value("v1"), g.Value(id("v1")))
assertVertexEqual(t, value(v0), g.ValueVertex(v0))
assertVertexEqual(t, value(v1), g.ValueVertex(v1))
assertVertexEqual(t,
value("v2",
junction("ej0", edge("e0", value("v0")), edge("e1", value("v1"))),
value(v2,
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
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
e0 := ValueOut(id("v0"), id("e0"))
g0 := Null.AddValueIn(e0, id("v1"))
e0 := ValueOut(v0, e0)
g0 := Null.AddValueIn(e0, v1)
assertNotEqual(g0, Null)
assertEqual(g0, g0)
// 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 := g0.AddValueIn(ValueOut(id("v0"), id("e1a")), id("v2"))
g1b := g0.AddValueIn(ValueOut(id("v0"), id("e1b")), id("v2"))
g1a := g0.AddValueIn(ValueOut(v0, e1a), v2)
g1b := g0.AddValueIn(ValueOut(v0, e1b), v2)
assertNotEqual(g1a, g1b)
}
{ // equal construction should yield equality, even if out of order
ga := Null.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
ga = ga.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2"))
gb := Null.AddValueIn(ValueOut(id("v1"), id("e1")), id("v2"))
gb = gb.AddValueIn(ValueOut(id("v0"), id("e0")), id("v1"))
ga := Null.AddValueIn(ValueOut(v0, e0), v1)
ga = ga.AddValueIn(ValueOut(v1, e1), v2)
gb := Null.AddValueIn(ValueOut(v1, e1), v2)
gb = gb.AddValueIn(ValueOut(v0, e0), v1)
assertEqual(ga, gb)
}
ej := NewValue("ej")
{ // junction basic test
e0 := ValueOut(id("v0"), id("e0"))
e1 := ValueOut(id("v1"), id("e1"))
ga := Null.AddValueIn(JunctionOut([]OpenEdge{e0, e1}, id("ej")), id("v2"))
gb := Null.AddValueIn(JunctionOut([]OpenEdge{e1, e0}, id("ej")), id("v2"))
e0 := ValueOut(v0, e0)
e1 := ValueOut(v1, e1)
ga := Null.AddValueIn(JunctionOut([]OpenEdge{e0, e1}, ej), v2)
gb := Null.AddValueIn(JunctionOut([]OpenEdge{e1, e0}, ej), v2)
assertEqual(ga, ga)
assertNotEqual(ga, gb)
}

View File

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

View File

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

View File

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

View File

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